diff options
Diffstat (limited to 'test/web')
123 files changed, 12655 insertions, 8591 deletions
diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index fa2364bd2..9151034da 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -1,19 +1,24 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do use Pleroma.Web.ConnCase + use Oban.Testing, repo: Pleroma.Repo + import Pleroma.Factory alias Pleroma.Activity + alias Pleroma.Delivery alias Pleroma.Instances alias Pleroma.Object + alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.Web.ActivityPub.ObjectView alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.UserView alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.CommonAPI + alias Pleroma.Workers.ReceiverWorker setup_all do Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -105,6 +110,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do assert json_response(conn, 200) == UserView.render("user.json", %{user: user}) end + + test "it returns 404 for remote users", %{ + conn: conn + } do + user = insert(:user, local: false, nickname: "remoteuser@example.com") + + conn = + conn + |> put_req_header("accept", "application/json") + |> get("/users/#{user.nickname}.json") + + assert json_response(conn, 404) + end end describe "/object/:uuid" do @@ -280,7 +298,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do assert json_response(conn1, :ok) assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"})) - Activity.delete_by_ap_id(activity.object.data["id"]) + Activity.delete_all_by_object_ap_id(activity.object.data["id"]) conn2 = conn @@ -302,7 +320,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do |> post("/inbox", data) assert "ok" == json_response(conn, 200) - :timer.sleep(500) + + ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) assert Activity.get_by_ap_id(data["id"]) end @@ -344,10 +363,91 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do |> post("/users/#{user.nickname}/inbox", data) assert "ok" == json_response(conn, 200) - :timer.sleep(500) + ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) assert Activity.get_by_ap_id(data["id"]) end + test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do + user = insert(:user) + + data = + Map.put(data, "to", user.ap_id) + |> Map.delete("cc") + + conn = + conn + |> assign(:valid_signature, true) + |> put_req_header("content-type", "application/activity+json") + |> post("/users/#{user.nickname}/inbox", data) + + assert "ok" == json_response(conn, 200) + ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) + assert Activity.get_by_ap_id(data["id"]) + end + + test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do + user = insert(:user) + + data = + Map.put(data, "cc", user.ap_id) + |> Map.delete("to") + + conn = + conn + |> assign(:valid_signature, true) + |> put_req_header("content-type", "application/activity+json") + |> post("/users/#{user.nickname}/inbox", data) + + assert "ok" == json_response(conn, 200) + ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) + %Activity{} = activity = Activity.get_by_ap_id(data["id"]) + assert user.ap_id in activity.recipients + end + + test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do + user = insert(:user) + + data = + Map.put(data, "bcc", user.ap_id) + |> Map.delete("to") + |> Map.delete("cc") + + conn = + conn + |> assign(:valid_signature, true) + |> put_req_header("content-type", "application/activity+json") + |> post("/users/#{user.nickname}/inbox", data) + + assert "ok" == json_response(conn, 200) + ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) + assert Activity.get_by_ap_id(data["id"]) + end + + test "it accepts announces with to as string instead of array", %{conn: conn} do + user = insert(:user) + + data = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "actor" => "http://mastodon.example.org/users/admin", + "id" => "http://mastodon.example.org/users/admin/statuses/19512778738411822/activity", + "object" => "https://mastodon.social/users/emelie/statuses/101849165031453009", + "to" => "https://www.w3.org/ns/activitystreams#Public", + "cc" => [user.ap_id], + "type" => "Announce" + } + + conn = + conn + |> assign(:valid_signature, true) + |> put_req_header("content-type", "application/activity+json") + |> post("/users/#{user.nickname}/inbox", data) + + assert "ok" == json_response(conn, 200) + ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) + %Activity{} = activity = Activity.get_by_ap_id(data["id"]) + assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients + end + test "it accepts messages from actors that are followed by the user", %{ conn: conn, data: data @@ -373,7 +473,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do |> post("/users/#{recipient.nickname}/inbox", data) assert "ok" == json_response(conn, 200) - :timer.sleep(500) + ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) assert Activity.get_by_ap_id(data["id"]) end @@ -463,6 +563,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do |> post("/users/#{recipient.nickname}/inbox", data) |> json_response(200) + ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) + activity = Activity.get_by_ap_id(data["id"]) assert activity.id @@ -538,6 +640,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do |> post("/users/#{user.nickname}/outbox", data) result = json_response(conn, 201) + assert Activity.get_by_ap_id(result["id"]) end @@ -674,7 +777,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do test "it returns returns a uri if the user has 'hide_followers' set", %{conn: conn} do user = insert(:user) - user_two = insert(:user, %{info: %{hide_followers: true}}) + user_two = insert(:user, hide_followers: true) User.follow(user, user_two) result = @@ -687,7 +790,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is not authenticated", %{conn: conn} do - user = insert(:user, %{info: %{hide_followers: true}}) + user = insert(:user, hide_followers: true) result = conn @@ -699,7 +802,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user", %{conn: conn} do - user = insert(:user, %{info: %{hide_followers: true}}) + user = insert(:user, hide_followers: true) other_user = insert(:user) {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) @@ -755,7 +858,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do end test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do - user = insert(:user, %{info: %{hide_follows: true}}) + user = insert(:user, hide_follows: true) user_two = insert(:user) User.follow(user, user_two) @@ -769,7 +872,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is not authenticated", %{conn: conn} do - user = insert(:user, %{info: %{hide_follows: true}}) + user = insert(:user, hide_follows: true) result = conn @@ -781,7 +884,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user", %{conn: conn} do - user = insert(:user, %{info: %{hide_follows: true}}) + user = insert(:user, hide_follows: true) other_user = insert(:user) {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user) @@ -822,4 +925,126 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do assert result["totalItems"] == 15 end end + + describe "delivery tracking" do + test "it tracks a signed object fetch", %{conn: conn} do + user = insert(:user, local: false) + activity = insert(:note_activity) + object = Object.normalize(activity) + + object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url()) + + conn + |> put_req_header("accept", "application/activity+json") + |> assign(:user, user) + |> get(object_path) + |> json_response(200) + + assert Delivery.get(object.id, user.id) + end + + test "it tracks a signed activity fetch", %{conn: conn} do + user = insert(:user, local: false) + activity = insert(:note_activity) + object = Object.normalize(activity) + + activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url()) + + conn + |> put_req_header("accept", "application/activity+json") + |> assign(:user, user) + |> get(activity_path) + |> json_response(200) + + assert Delivery.get(object.id, user.id) + end + + test "it tracks a signed object fetch when the json is cached", %{conn: conn} do + user = insert(:user, local: false) + other_user = insert(:user, local: false) + activity = insert(:note_activity) + object = Object.normalize(activity) + + object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url()) + + conn + |> put_req_header("accept", "application/activity+json") + |> assign(:user, user) + |> get(object_path) + |> json_response(200) + + build_conn() + |> put_req_header("accept", "application/activity+json") + |> assign(:user, other_user) + |> get(object_path) + |> json_response(200) + + assert Delivery.get(object.id, user.id) + assert Delivery.get(object.id, other_user.id) + end + + test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do + user = insert(:user, local: false) + other_user = insert(:user, local: false) + activity = insert(:note_activity) + object = Object.normalize(activity) + + activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url()) + + conn + |> put_req_header("accept", "application/activity+json") + |> assign(:user, user) + |> get(activity_path) + |> json_response(200) + + build_conn() + |> put_req_header("accept", "application/activity+json") + |> assign(:user, other_user) + |> get(activity_path) + |> json_response(200) + + assert Delivery.get(object.id, user.id) + assert Delivery.get(object.id, other_user.id) + end + end + + describe "Additionnal ActivityPub C2S endpoints" do + test "/api/ap/whoami", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> get("/api/ap/whoami") + + user = User.get_cached_by_id(user.id) + + assert UserView.render("user.json", %{user: user}) == json_response(conn, 200) + end + + clear_config([:media_proxy]) + clear_config([Pleroma.Upload]) + + test "uploadMedia", %{conn: conn} do + user = insert(:user) + + desc = "Description of the image" + + image = %Plug.Upload{ + content_type: "image/jpg", + path: Path.absname("test/fixtures/image.jpg"), + filename: "an_image.jpg" + } + + conn = + conn + |> assign(:user, user) + |> post("/api/ap/upload_media", %{"file" => image, "description" => desc}) + + assert object = json_response(conn, :created) + assert object["name"] == desc + assert object["type"] == "Document" + assert object["actor"] == user.ap_id + end + end end diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index fecbf5353..3dd3dd04d 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -1,16 +1,22 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.ActivityPubTest do use Pleroma.DataCase + use Oban.Testing, repo: Pleroma.Repo + alias Pleroma.Activity alias Pleroma.Builders.ActivityBuilder + alias Pleroma.Config + alias Pleroma.Notification alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.AdminAPI.AccountView alias Pleroma.Web.CommonAPI + alias Pleroma.Web.Federator import Pleroma.Factory import Tesla.Mock @@ -41,6 +47,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert called(Pleroma.Web.Streamer.stream("participation", participations)) end end + + test "streams them out on activity creation" do + user_one = insert(:user) + user_two = insert(:user) + + with_mock Pleroma.Web.Streamer, + stream: fn _, _ -> nil end do + {:ok, activity} = + CommonAPI.post(user_one, %{ + "status" => "@#{user_two.nickname}", + "visibility" => "direct" + }) + + conversation = + activity.data["context"] + |> Pleroma.Conversation.get_for_ap_id() + |> Repo.preload(participations: :user) + + assert called(Pleroma.Web.Streamer.stream("participation", conversation.participations)) + end + end end describe "fetching restricted by visibility" do @@ -87,17 +114,83 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do end end + describe "fetching excluded by visibility" do + test "it excludes by the appropriate visibility" do + user = insert(:user) + + {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"}) + + {:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) + + {:ok, unlisted_activity} = + CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"}) + + {:ok, private_activity} = + CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + + activities = + ActivityPub.fetch_activities([], %{ + "exclude_visibilities" => "direct", + "actor_id" => user.ap_id + }) + + assert public_activity in activities + assert unlisted_activity in activities + assert private_activity in activities + refute direct_activity in activities + + activities = + ActivityPub.fetch_activities([], %{ + "exclude_visibilities" => "unlisted", + "actor_id" => user.ap_id + }) + + assert public_activity in activities + refute unlisted_activity in activities + assert private_activity in activities + assert direct_activity in activities + + activities = + ActivityPub.fetch_activities([], %{ + "exclude_visibilities" => "private", + "actor_id" => user.ap_id + }) + + assert public_activity in activities + assert unlisted_activity in activities + refute private_activity in activities + assert direct_activity in activities + + activities = + ActivityPub.fetch_activities([], %{ + "exclude_visibilities" => "public", + "actor_id" => user.ap_id + }) + + refute public_activity in activities + assert unlisted_activity in activities + assert private_activity in activities + assert direct_activity in activities + end + end + describe "building a user from his ap id" do test "it returns a user" do user_id = "http://mastodon.example.org/users/admin" {:ok, user} = ActivityPub.make_user_from_ap_id(user_id) assert user.ap_id == user_id assert user.nickname == "admin@mastodon.example.org" - assert user.info.source_data - assert user.info.ap_enabled + assert user.source_data + assert user.ap_enabled assert user.follower_address == "http://mastodon.example.org/users/admin/followers" end + test "it returns a user that is invisible" do + user_id = "http://mastodon.example.org/users/relay" + {:ok, user} = ActivityPub.make_user_from_ap_id(user_id) + assert User.invisible?(user) + end + test "it fetches the appropriate tag-restricted posts" do user = insert(:user) @@ -133,7 +226,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do describe "insertion" do test "drops activities beyond a certain limit" do - limit = Pleroma.Config.get([:instance, :remote_limit]) + limit = Config.get([:instance, :remote_limit]) random_text = :crypto.strong_rand_bytes(limit + 1) @@ -257,7 +350,64 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do end end + describe "listen activities" do + test "does not increase user note count" do + user = insert(:user) + + {:ok, activity} = + ActivityPub.listen(%{ + to: ["https://www.w3.org/ns/activitystreams#Public"], + actor: user, + context: "", + object: %{ + "actor" => user.ap_id, + "to" => ["https://www.w3.org/ns/activitystreams#Public"], + "artist" => "lain", + "title" => "lain radio episode 1", + "length" => 180_000, + "type" => "Audio" + } + }) + + assert activity.actor == user.ap_id + + user = User.get_cached_by_id(user.id) + assert user.note_count == 0 + end + + test "can be fetched into a timeline" do + _listen_activity_1 = insert(:listen) + _listen_activity_2 = insert(:listen) + _listen_activity_3 = insert(:listen) + + timeline = ActivityPub.fetch_activities([], %{"type" => ["Listen"]}) + + assert length(timeline) == 3 + end + end + describe "create activities" do + test "it reverts create" do + user = insert(:user) + + with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do + assert {:error, :reverted} = + ActivityPub.create(%{ + to: ["user1", "user2"], + actor: user, + context: "", + object: %{ + "to" => ["user1", "user2"], + "type" => "Note", + "content" => "testing" + } + }) + end + + assert Repo.aggregate(Activity, :count, :id) == 0 + assert Repo.aggregate(Object, :count, :id) == 0 + end + test "removes doubled 'to' recipients" do user = insert(:user) @@ -306,7 +456,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do }) user = User.get_cached_by_id(user.id) - assert user.info.note_count == 2 + assert user.note_count == 2 end test "increases replies count" do @@ -360,7 +510,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do activity_five = insert(:note_activity) user = insert(:user) - {:ok, user} = User.block(user, %{ap_id: activity_five.data["actor"]}) + {:ok, _user_relationship} = User.block(user, %{ap_id: activity_five.data["actor"]}) activities = ActivityPub.fetch_activities_for_context("2hu", %{"blocking_user" => user}) assert activities == [activity_two, activity] @@ -373,7 +523,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do activity_three = insert(:note_activity) user = insert(:user) booster = insert(:user) - {:ok, user} = User.block(user, %{ap_id: activity_one.data["actor"]}) + {:ok, _user_relationship} = User.block(user, %{ap_id: activity_one.data["actor"]}) activities = ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true}) @@ -382,7 +532,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert Enum.member?(activities, activity_three) refute Enum.member?(activities, activity_one) - {:ok, user} = User.unblock(user, %{ap_id: activity_one.data["actor"]}) + {:ok, _user_block} = User.unblock(user, %{ap_id: activity_one.data["actor"]}) activities = ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true}) @@ -391,7 +541,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert Enum.member?(activities, activity_three) assert Enum.member?(activities, activity_one) - {:ok, user} = User.block(user, %{ap_id: activity_three.data["actor"]}) + {:ok, _user_relationship} = User.block(user, %{ap_id: activity_three.data["actor"]}) {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster) %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id) activity_three = Activity.get_by_id(activity_three.id) @@ -418,7 +568,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do blockee = insert(:user) friend = insert(:user) - {:ok, blocker} = User.block(blocker, blockee) + {:ok, _user_relationship} = User.block(blocker, blockee) {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"}) @@ -441,7 +591,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do blockee = insert(:user) friend = insert(:user) - {:ok, blocker} = User.block(blocker, blockee) + {:ok, _user_relationship} = User.block(blocker, blockee) {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"}) @@ -481,13 +631,48 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do refute repeat_activity in activities end + test "does return activities from followed users on blocked domains" do + domain = "meanies.social" + domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"}) + blocker = insert(:user) + + {:ok, blocker} = User.follow(blocker, domain_user) + {:ok, blocker} = User.block_domain(blocker, domain) + + assert User.following?(blocker, domain_user) + assert User.blocks_domain?(blocker, domain_user) + refute User.blocks?(blocker, domain_user) + + note = insert(:note, %{data: %{"actor" => domain_user.ap_id}}) + activity = insert(:note_activity, %{note: note}) + + activities = + ActivityPub.fetch_activities([], %{"blocking_user" => blocker, "skip_preload" => true}) + + assert activity in activities + + # And check that if the guy we DO follow boosts someone else from their domain, + # that should be hidden + another_user = insert(:user, %{ap_id: "https://#{domain}/@meanie2"}) + bad_note = insert(:note, %{data: %{"actor" => another_user.ap_id}}) + bad_activity = insert(:note_activity, %{note: bad_note}) + {:ok, repeat_activity, _} = CommonAPI.repeat(bad_activity.id, domain_user) + + activities = + ActivityPub.fetch_activities([], %{"blocking_user" => blocker, "skip_preload" => true}) + + refute repeat_activity in activities + end + test "doesn't return muted activities" do activity_one = insert(:note_activity) activity_two = insert(:note_activity) activity_three = insert(:note_activity) user = insert(:user) booster = insert(:user) - {:ok, user} = User.mute(user, %User{ap_id: activity_one.data["actor"]}) + + activity_one_actor = User.get_by_ap_id(activity_one.data["actor"]) + {:ok, _user_relationships} = User.mute(user, activity_one_actor) activities = ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true}) @@ -508,7 +693,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert Enum.member?(activities, activity_three) assert Enum.member?(activities, activity_one) - {:ok, user} = User.unmute(user, %User{ap_id: activity_one.data["actor"]}) + {:ok, _user_mute} = User.unmute(user, activity_one_actor) activities = ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true}) @@ -517,7 +702,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert Enum.member?(activities, activity_three) assert Enum.member?(activities, activity_one) - {:ok, user} = User.mute(user, %User{ap_id: activity_three.data["actor"]}) + activity_three_actor = User.get_by_ap_id(activity_three.data["actor"]) + {:ok, _user_relationships} = User.mute(user, activity_three_actor) {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster) %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id) activity_three = Activity.get_by_id(activity_three.id) @@ -570,7 +756,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do {:ok, announce, _object} = CommonAPI.repeat(activity_three.id, booster) - [announce_activity] = ActivityPub.fetch_activities([user.ap_id | user.following]) + [announce_activity] = ActivityPub.fetch_activities([user.ap_id | User.following(user)]) assert announce_activity.id == announce.id end @@ -610,48 +796,61 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do end test "retrieves a maximum of 20 activities" do - activities = ActivityBuilder.insert_list(30) - last_expected = List.last(activities) + ActivityBuilder.insert_list(10) + expected_activities = ActivityBuilder.insert_list(20) activities = ActivityPub.fetch_public_activities() - last = List.last(activities) + assert collect_ids(activities) == collect_ids(expected_activities) assert length(activities) == 20 - assert last == last_expected end test "retrieves ids starting from a since_id" do activities = ActivityBuilder.insert_list(30) - later_activities = ActivityBuilder.insert_list(10) + expected_activities = ActivityBuilder.insert_list(10) since_id = List.last(activities).id - last_expected = List.last(later_activities) activities = ActivityPub.fetch_public_activities(%{"since_id" => since_id}) - last = List.last(activities) + assert collect_ids(activities) == collect_ids(expected_activities) assert length(activities) == 10 - assert last == last_expected end test "retrieves ids up to max_id" do - _first_activities = ActivityBuilder.insert_list(10) - activities = ActivityBuilder.insert_list(20) - later_activities = ActivityBuilder.insert_list(10) - max_id = List.first(later_activities).id - last_expected = List.last(activities) + ActivityBuilder.insert_list(10) + expected_activities = ActivityBuilder.insert_list(20) + + %{id: max_id} = + 10 + |> ActivityBuilder.insert_list() + |> List.first() activities = ActivityPub.fetch_public_activities(%{"max_id" => max_id}) - last = List.last(activities) assert length(activities) == 20 - assert last == last_expected + assert collect_ids(activities) == collect_ids(expected_activities) + end + + test "paginates via offset/limit" do + _first_part_activities = ActivityBuilder.insert_list(10) + second_part_activities = ActivityBuilder.insert_list(10) + + later_activities = ActivityBuilder.insert_list(10) + + activities = + ActivityPub.fetch_public_activities(%{"page" => "2", "page_size" => "20"}, :offset) + + assert length(activities) == 20 + + assert collect_ids(activities) == + collect_ids(second_part_activities) ++ collect_ids(later_activities) end test "doesn't return reblogs for users for whom reblogs have been muted" do activity = insert(:note_activity) user = insert(:user) booster = insert(:user) - {:ok, user} = CommonAPI.hide_reblogs(user, booster) + {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster) {:ok, activity, _} = CommonAPI.repeat(activity.id, booster) @@ -664,8 +863,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do activity = insert(:note_activity) user = insert(:user) booster = insert(:user) - {:ok, user} = CommonAPI.hide_reblogs(user, booster) - {:ok, user} = CommonAPI.show_reblogs(user, booster) + {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster) + {:ok, _reblog_mute} = CommonAPI.show_reblogs(user, booster) {:ok, activity, _} = CommonAPI.repeat(activity.id, booster) @@ -675,16 +874,137 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do end end + describe "react to an object" do + test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do + Config.put([:instance, :federating], true) + user = insert(:user) + reactor = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"}) + assert object = Object.normalize(activity) + + {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "🔥") + + assert called(Federator.publish(reaction_activity)) + end + + test "adds an emoji reaction activity to the db" do + user = insert(:user) + reactor = insert(:user) + third_user = insert(:user) + fourth_user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"}) + assert object = Object.normalize(activity) + + {:ok, reaction_activity, object} = ActivityPub.react_with_emoji(reactor, object, "🔥") + + assert reaction_activity + + assert reaction_activity.data["actor"] == reactor.ap_id + assert reaction_activity.data["type"] == "EmojiReact" + assert reaction_activity.data["content"] == "🔥" + assert reaction_activity.data["object"] == object.data["id"] + assert reaction_activity.data["to"] == [User.ap_followers(reactor), activity.data["actor"]] + assert reaction_activity.data["context"] == object.data["context"] + assert object.data["reaction_count"] == 1 + assert object.data["reactions"] == [["🔥", [reactor.ap_id]]] + + {:ok, _reaction_activity, object} = ActivityPub.react_with_emoji(third_user, object, "☕") + + assert object.data["reaction_count"] == 2 + assert object.data["reactions"] == [["🔥", [reactor.ap_id]], ["☕", [third_user.ap_id]]] + + {:ok, _reaction_activity, object} = ActivityPub.react_with_emoji(fourth_user, object, "🔥") + + assert object.data["reaction_count"] == 3 + + assert object.data["reactions"] == [ + ["🔥", [fourth_user.ap_id, reactor.ap_id]], + ["☕", [third_user.ap_id]] + ] + end + + test "reverts emoji reaction on error" do + [user, reactor] = insert_list(2, :user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "Status"}) + object = Object.normalize(activity) + + with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do + assert {:error, :reverted} = ActivityPub.react_with_emoji(reactor, object, "😀") + end + + object = Object.get_by_ap_id(object.data["id"]) + refute object.data["reaction_count"] + refute object.data["reactions"] + end + end + + describe "unreacting to an object" do + test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do + Config.put([:instance, :federating], true) + user = insert(:user) + reactor = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"}) + assert object = Object.normalize(activity) + + {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "🔥") + + assert called(Federator.publish(reaction_activity)) + + {:ok, unreaction_activity, _object} = + ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"]) + + assert called(Federator.publish(unreaction_activity)) + end + + test "adds an undo activity to the db" do + user = insert(:user) + reactor = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"}) + assert object = Object.normalize(activity) + + {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "🔥") + + {:ok, unreaction_activity, _object} = + ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"]) + + assert unreaction_activity.actor == reactor.ap_id + assert unreaction_activity.data["object"] == reaction_activity.data["id"] + + object = Object.get_by_ap_id(object.data["id"]) + assert object.data["reaction_count"] == 0 + assert object.data["reactions"] == [] + end + + test "reverts emoji unreact on error" do + [user, reactor] = insert_list(2, :user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "Status"}) + object = Object.normalize(activity) + + {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "😀") + + with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do + assert {:error, :reverted} = + ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"]) + end + + object = Object.get_by_ap_id(object.data["id"]) + + assert object.data["reaction_count"] == 1 + assert object.data["reactions"] == [["😀", [reactor.ap_id]]] + end + end + describe "like an object" do - test_with_mock "sends an activity to federation", Pleroma.Web.Federator, [:passthrough], [] do - Pleroma.Config.put([:instance, :federating], true) + test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do + Config.put([:instance, :federating], true) note_activity = insert(:note_activity) assert object_activity = Object.normalize(note_activity) user = insert(:user) {:ok, like_activity, _object} = ActivityPub.like(user, object_activity) - assert called(Pleroma.Web.Federator.publish(like_activity, 5)) + assert called(Federator.publish(like_activity)) end test "returns exist activity if object already liked" do @@ -699,6 +1019,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert like_activity == like_activity_exist end + test "reverts like activity on error" do + note_activity = insert(:note_activity) + object = Object.normalize(note_activity) + user = insert(:user) + + with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do + assert {:error, :reverted} = ActivityPub.like(user, object) + end + + assert Repo.aggregate(Activity, :count, :id) == 1 + assert Repo.get(Object, object.id) == object + end + test "adds a like activity to the db" do note_activity = insert(:note_activity) assert object = Object.normalize(note_activity) @@ -729,15 +1062,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do end describe "unliking" do - test_with_mock "sends an activity to federation", Pleroma.Web.Federator, [:passthrough], [] do - Pleroma.Config.put([:instance, :federating], true) + test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do + Config.put([:instance, :federating], true) note_activity = insert(:note_activity) object = Object.normalize(note_activity) user = insert(:user) {:ok, object} = ActivityPub.unlike(user, object) - refute called(Pleroma.Web.Federator.publish()) + refute called(Federator.publish()) {:ok, _like_activity, object} = ActivityPub.like(user, object) assert object.data["like_count"] == 1 @@ -745,7 +1078,24 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object) assert object.data["like_count"] == 0 - assert called(Pleroma.Web.Federator.publish(unlike_activity, 5)) + assert called(Federator.publish(unlike_activity)) + end + + test "reverts unliking on error" do + note_activity = insert(:note_activity) + object = Object.normalize(note_activity) + user = insert(:user) + + {:ok, like_activity, object} = ActivityPub.like(user, object) + assert object.data["like_count"] == 1 + + with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do + assert {:error, :reverted} = ActivityPub.unlike(user, object) + end + + assert Object.get_by_ap_id(object.data["id"]) == object + assert object.data["like_count"] == 1 + assert Activity.get_by_id(like_activity.id) end test "unliking a previously liked object" do @@ -787,6 +1137,54 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert announce_activity.data["actor"] == user.ap_id assert announce_activity.data["context"] == object.data["context"] end + + test "reverts annouce from object on error" do + note_activity = insert(:note_activity) + object = Object.normalize(note_activity) + user = insert(:user) + + with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do + assert {:error, :reverted} = ActivityPub.announce(user, object) + end + + reloaded_object = Object.get_by_ap_id(object.data["id"]) + assert reloaded_object == object + refute reloaded_object.data["announcement_count"] + refute reloaded_object.data["announcements"] + end + end + + describe "announcing a private object" do + test "adds an announce activity to the db if the audience is not widened" do + user = insert(:user) + {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + object = Object.normalize(note_activity) + + {:ok, announce_activity, object} = ActivityPub.announce(user, object, nil, true, false) + + assert announce_activity.data["to"] == [User.ap_followers(user)] + + assert announce_activity.data["object"] == object.data["id"] + assert announce_activity.data["actor"] == user.ap_id + assert announce_activity.data["context"] == object.data["context"] + end + + test "does not add an announce activity to the db if the audience is widened" do + user = insert(:user) + {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + object = Object.normalize(note_activity) + + assert {:error, _} = ActivityPub.announce(user, object, nil, true, true) + end + + test "does not add an announce activity to the db if the announcer is not the author" do + user = insert(:user) + announcer = insert(:user) + {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + object = Object.normalize(note_activity) + + assert {:error, _} = ActivityPub.announce(announcer, object, nil, true, false) + end end describe "unannouncing an object" do @@ -796,8 +1194,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do user = insert(:user) # Unannouncing an object that is not announced does nothing - # {:ok, object} = ActivityPub.unannounce(user, object) - # assert object.data["announcement_count"] == 0 + {:ok, object} = ActivityPub.unannounce(user, object) + refute object.data["announcement_count"] {:ok, announce_activity, object} = ActivityPub.announce(user, object) assert object.data["announcement_count"] == 1 @@ -817,6 +1215,22 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert Activity.get_by_id(announce_activity.id) == nil end + + test "reverts unannouncing on error" do + note_activity = insert(:note_activity) + object = Object.normalize(note_activity) + user = insert(:user) + + {:ok, _announce_activity, object} = ActivityPub.announce(user, object) + assert object.data["announcement_count"] == 1 + + with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do + assert {:error, :reverted} = ActivityPub.unannounce(user, object) + end + + object = Object.get_by_ap_id(object.data["id"]) + assert object.data["announcement_count"] == 1 + end end describe "uploading files" do @@ -851,6 +1265,35 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do end describe "following / unfollowing" do + test "it reverts follow activity" do + follower = insert(:user) + followed = insert(:user) + + with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do + assert {:error, :reverted} = ActivityPub.follow(follower, followed) + end + + assert Repo.aggregate(Activity, :count, :id) == 0 + assert Repo.aggregate(Object, :count, :id) == 0 + end + + test "it reverts unfollow activity" do + follower = insert(:user) + followed = insert(:user) + + {:ok, follow_activity} = ActivityPub.follow(follower, followed) + + with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do + assert {:error, :reverted} = ActivityPub.unfollow(follower, followed) + end + + activity = Activity.get_by_id(follow_activity.id) + assert activity.data["type"] == "Follow" + assert activity.data["actor"] == follower.ap_id + + assert activity.data["object"] == followed.ap_id + end + test "creates a follow activity" do follower = insert(:user) followed = insert(:user) @@ -877,9 +1320,37 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert embedded_object["object"] == followed.ap_id assert embedded_object["id"] == follow_activity.data["id"] end + + test "creates an undo activity for a pending follow request" do + follower = insert(:user) + followed = insert(:user, %{locked: true}) + + {:ok, follow_activity} = ActivityPub.follow(follower, followed) + {:ok, activity} = ActivityPub.unfollow(follower, followed) + + assert activity.data["type"] == "Undo" + assert activity.data["actor"] == follower.ap_id + + embedded_object = activity.data["object"] + assert is_map(embedded_object) + assert embedded_object["type"] == "Follow" + assert embedded_object["object"] == followed.ap_id + assert embedded_object["id"] == follow_activity.data["id"] + end end describe "blocking / unblocking" do + test "reverts block activity on error" do + [blocker, blocked] = insert_list(2, :user) + + with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do + assert {:error, :reverted} = ActivityPub.block(blocker, blocked) + end + + assert Repo.aggregate(Activity, :count, :id) == 0 + assert Repo.aggregate(Object, :count, :id) == 0 + end + test "creates a block activity" do blocker = insert(:user) blocked = insert(:user) @@ -891,6 +1362,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert activity.data["object"] == blocked.ap_id end + test "reverts unblock activity on error" do + [blocker, blocked] = insert_list(2, :user) + {:ok, block_activity} = ActivityPub.block(blocker, blocked) + + with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do + assert {:error, :reverted} = ActivityPub.unblock(blocker, blocked) + end + + assert block_activity.data["type"] == "Block" + assert block_activity.data["actor"] == blocker.ap_id + + assert Repo.aggregate(Activity, :count, :id) == 1 + assert Repo.aggregate(Object, :count, :id) == 1 + end + test "creates an undo activity for the last block" do blocker = insert(:user) blocked = insert(:user) @@ -910,6 +1396,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do end describe "deletion" do + clear_config([:instance, :rewrite_policy]) + + test "it reverts deletion on error" do + note = insert(:note_activity) + object = Object.normalize(note) + + with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do + assert {:error, :reverted} = ActivityPub.delete(object) + end + + assert Repo.aggregate(Activity, :count, :id) == 1 + assert Repo.get(Object, object.id) == object + assert Activity.get_by_id(note.id) == note + end + test "it creates a delete activity and deletes the original object" do note = insert(:note_activity) object = Object.normalize(note) @@ -925,7 +1426,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do end test "decrements user note count only for public activities" do - user = insert(:user, info: %{note_count: 10}) + user = insert(:user, note_count: 10) {:ok, a1} = CommonAPI.post(User.get_cached_by_id(user.id), %{ @@ -957,7 +1458,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do {:ok, _} = Object.normalize(a4) |> ActivityPub.delete() user = User.get_cached_by_id(user.id) - assert user.info.note_count == 10 + assert user.note_count == 10 end test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do @@ -1011,6 +1512,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) assert object.data["repliesCount"] == 0 end + + test "it passes delete activity through MRF before deleting the object" do + Pleroma.Config.put([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.DropPolicy) + + note = insert(:note_activity) + object = Object.normalize(note) + + {:error, {:reject, _}} = ActivityPub.delete(object) + + assert Activity.get_by_id(note.id) + assert Repo.get(Object, object.id).data["type"] == object.data["type"] + end end describe "timeline post-processing" do @@ -1048,7 +1561,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do }) activities = - ActivityPub.fetch_activities([user1.ap_id | user1.following]) + ActivityPub.fetch_activities([user1.ap_id | User.following(user1)]) |> Enum.map(fn a -> a.id end) private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"]) @@ -1058,7 +1571,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert length(activities) == 3 activities = - ActivityPub.fetch_activities([user1.ap_id | user1.following], %{"user" => user1}) + ActivityPub.fetch_activities([user1.ap_id | User.following(user1)], %{"user" => user1}) |> Enum.map(fn a -> a.id end) assert [public_activity.id, private_activity_1.id] == activities @@ -1067,6 +1580,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do end describe "update" do + clear_config([:instance, :max_pinned_statuses]) + test "it creates an update activity with the new user data" do user = insert(:user) {:ok, user} = User.ensure_keys_present(user) @@ -1089,7 +1604,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do end test "returned pinned statuses" do - Pleroma.Config.put([:instance, :max_pinned_statuses], 3) + Config.put([:instance, :max_pinned_statuses], 3) user = insert(:user) {:ok, activity_one} = CommonAPI.post(user, %{"status" => "HI!!!"}) @@ -1110,35 +1625,99 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert 3 = length(activities) end - test "it can create a Flag activity" do - reporter = insert(:user) - target_account = insert(:user) - {:ok, activity} = CommonAPI.post(target_account, %{"status" => "foobar"}) - context = Utils.generate_context_id() - content = "foobar" - - reporter_ap_id = reporter.ap_id - target_ap_id = target_account.ap_id - activity_ap_id = activity.data["id"] - - assert {:ok, activity} = - ActivityPub.flag(%{ - actor: reporter, - context: context, - account: target_account, - statuses: [activity], - content: content - }) - - assert %Activity{ - actor: ^reporter_ap_id, - data: %{ - "type" => "Flag", - "content" => ^content, - "context" => ^context, - "object" => [^target_ap_id, ^activity_ap_id] - } - } = activity + describe "flag/1" do + setup do + reporter = insert(:user) + target_account = insert(:user) + content = "foobar" + {:ok, activity} = CommonAPI.post(target_account, %{"status" => content}) + context = Utils.generate_context_id() + + reporter_ap_id = reporter.ap_id + target_ap_id = target_account.ap_id + activity_ap_id = activity.data["id"] + + activity_with_object = Activity.get_by_ap_id_with_object(activity_ap_id) + + {:ok, + %{ + reporter: reporter, + context: context, + target_account: target_account, + reported_activity: activity, + content: content, + activity_ap_id: activity_ap_id, + activity_with_object: activity_with_object, + reporter_ap_id: reporter_ap_id, + target_ap_id: target_ap_id + }} + end + + test "it can create a Flag activity", + %{ + reporter: reporter, + context: context, + target_account: target_account, + reported_activity: reported_activity, + content: content, + activity_ap_id: activity_ap_id, + activity_with_object: activity_with_object, + reporter_ap_id: reporter_ap_id, + target_ap_id: target_ap_id + } do + assert {:ok, activity} = + ActivityPub.flag(%{ + actor: reporter, + context: context, + account: target_account, + statuses: [reported_activity], + content: content + }) + + note_obj = %{ + "type" => "Note", + "id" => activity_ap_id, + "content" => content, + "published" => activity_with_object.object.data["published"], + "actor" => AccountView.render("show.json", %{user: target_account}) + } + + assert %Activity{ + actor: ^reporter_ap_id, + data: %{ + "type" => "Flag", + "content" => ^content, + "context" => ^context, + "object" => [^target_ap_id, ^note_obj] + } + } = activity + end + + test_with_mock "strips status data from Flag, before federating it", + %{ + reporter: reporter, + context: context, + target_account: target_account, + reported_activity: reported_activity, + content: content + }, + Utils, + [:passthrough], + [] do + {:ok, activity} = + ActivityPub.flag(%{ + actor: reporter, + context: context, + account: target_account, + statuses: [reported_activity], + content: content + }) + + new_data = + put_in(activity.data, ["object"], [target_account.ap_id, reported_activity.data["id"]]) + + assert_called(Utils.maybe_federate(%{activity | data: new_data})) + end end test "fetch_activities/2 returns activities addressed to a list " do @@ -1221,9 +1800,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do following_address: "http://localhost:4001/users/masto_closed/following" ) - {:ok, info} = ActivityPub.fetch_follow_information_for_user(user) - assert info.hide_followers == true - assert info.hide_follows == false + {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user) + assert follow_info.hide_followers == true + assert follow_info.hide_follows == false end test "detects hidden follows" do @@ -1244,9 +1823,151 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do following_address: "http://localhost:4001/users/masto_closed/following" ) - {:ok, info} = ActivityPub.fetch_follow_information_for_user(user) - assert info.hide_followers == false - assert info.hide_follows == true + {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user) + assert follow_info.hide_followers == false + assert follow_info.hide_follows == true + end + + test "detects hidden follows/followers for friendica" do + user = + insert(:user, + local: false, + follower_address: "http://localhost:8080/followers/fuser3", + following_address: "http://localhost:8080/following/fuser3" + ) + + {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user) + assert follow_info.hide_followers == true + assert follow_info.follower_count == 296 + assert follow_info.following_count == 32 + assert follow_info.hide_follows == true + end + + test "doesn't crash when follower and following counters are hidden" do + mock(fn env -> + case env.url do + "http://localhost:4001/users/masto_hidden_counters/following" -> + json(%{ + "@context" => "https://www.w3.org/ns/activitystreams", + "id" => "http://localhost:4001/users/masto_hidden_counters/followers" + }) + + "http://localhost:4001/users/masto_hidden_counters/following?page=1" -> + %Tesla.Env{status: 403, body: ""} + + "http://localhost:4001/users/masto_hidden_counters/followers" -> + json(%{ + "@context" => "https://www.w3.org/ns/activitystreams", + "id" => "http://localhost:4001/users/masto_hidden_counters/following" + }) + + "http://localhost:4001/users/masto_hidden_counters/followers?page=1" -> + %Tesla.Env{status: 403, body: ""} + end + end) + + user = + insert(:user, + local: false, + follower_address: "http://localhost:4001/users/masto_hidden_counters/followers", + following_address: "http://localhost:4001/users/masto_hidden_counters/following" + ) + + {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user) + + assert follow_info.hide_followers == true + assert follow_info.follower_count == 0 + assert follow_info.hide_follows == true + assert follow_info.following_count == 0 + end + end + + describe "fetch_favourites/3" do + test "returns a favourite activities sorted by adds to favorite" do + user = insert(:user) + other_user = insert(:user) + user1 = insert(:user) + user2 = insert(:user) + {:ok, a1} = CommonAPI.post(user1, %{"status" => "bla"}) + {:ok, _a2} = CommonAPI.post(user2, %{"status" => "traps are happy"}) + {:ok, a3} = CommonAPI.post(user2, %{"status" => "Trees Are "}) + {:ok, a4} = CommonAPI.post(user2, %{"status" => "Agent Smith "}) + {:ok, a5} = CommonAPI.post(user1, %{"status" => "Red or Blue "}) + + {:ok, _, _} = CommonAPI.favorite(a4.id, user) + {:ok, _, _} = CommonAPI.favorite(a3.id, other_user) + {:ok, _, _} = CommonAPI.favorite(a3.id, user) + {:ok, _, _} = CommonAPI.favorite(a5.id, other_user) + {:ok, _, _} = CommonAPI.favorite(a5.id, user) + {:ok, _, _} = CommonAPI.favorite(a4.id, other_user) + {:ok, _, _} = CommonAPI.favorite(a1.id, user) + {:ok, _, _} = CommonAPI.favorite(a1.id, other_user) + result = ActivityPub.fetch_favourites(user) + + assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id] + + result = ActivityPub.fetch_favourites(user, %{"limit" => 2}) + assert Enum.map(result, & &1.id) == [a1.id, a5.id] + end + end + + describe "Move activity" do + test "create" do + %{ap_id: old_ap_id} = old_user = insert(:user) + %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id]) + follower = insert(:user) + follower_move_opted_out = insert(:user, allow_following_move: false) + + User.follow(follower, old_user) + User.follow(follower_move_opted_out, old_user) + + assert User.following?(follower, old_user) + assert User.following?(follower_move_opted_out, old_user) + + assert {:ok, activity} = ActivityPub.move(old_user, new_user) + + assert %Activity{ + actor: ^old_ap_id, + data: %{ + "actor" => ^old_ap_id, + "object" => ^old_ap_id, + "target" => ^new_ap_id, + "type" => "Move" + }, + local: true + } = activity + + params = %{ + "op" => "move_following", + "origin_id" => old_user.id, + "target_id" => new_user.id + } + + assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params) + + Pleroma.Workers.BackgroundWorker.perform(params, nil) + + refute User.following?(follower, old_user) + assert User.following?(follower, new_user) + + assert User.following?(follower_move_opted_out, old_user) + refute User.following?(follower_move_opted_out, new_user) + + activity = %Activity{activity | object: nil} + + assert [%Notification{activity: ^activity}] = + Notification.for_user(follower, %{with_move: true}) + + assert [%Notification{activity: ^activity}] = + Notification.for_user(follower_move_opted_out, %{with_move: true}) + end + + test "old user must be in the new user's `also_known_as` list" do + old_user = insert(:user) + new_user = insert(:user) + + assert {:error, "Target account must have the origin in `alsoKnownAs`"} = + ActivityPub.move(old_user, new_user) end end end diff --git a/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs b/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs index 03dc299ec..b524fdd23 100644 --- a/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs +++ b/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs @@ -35,7 +35,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do test "it allows posts without links" do user = insert(:user) - assert user.info.note_count == 0 + assert user.note_count == 0 message = @linkless_message @@ -47,7 +47,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do test "it disallows posts with links" do user = insert(:user) - assert user.info.note_count == 0 + assert user.note_count == 0 message = @linkful_message @@ -59,9 +59,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do describe "with old user" do test "it allows posts without links" do - user = insert(:user, info: %{note_count: 1}) + user = insert(:user, note_count: 1) - assert user.info.note_count == 1 + assert user.note_count == 1 message = @linkless_message @@ -71,9 +71,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do end test "it allows posts with links" do - user = insert(:user, info: %{note_count: 1}) + user = insert(:user, note_count: 1) - assert user.info.note_count == 1 + assert user.note_count == 1 message = @linkful_message @@ -85,9 +85,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do describe "with followed new user" do test "it allows posts without links" do - user = insert(:user, info: %{follower_count: 1}) + user = insert(:user, follower_count: 1) - assert user.info.follower_count == 1 + assert user.follower_count == 1 message = @linkless_message @@ -97,9 +97,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do end test "it allows posts with links" do - user = insert(:user, info: %{follower_count: 1}) + user = insert(:user, follower_count: 1) - assert user.info.follower_count == 1 + assert user.follower_count == 1 message = @linkful_message @@ -133,7 +133,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do describe "with contentless-objects" do test "it does not reject them or error out" do - user = insert(:user, info: %{note_count: 1}) + user = insert(:user, note_count: 1) message = @response_message diff --git a/test/web/activity_pub/mrf/hellthread_policy_test.exs b/test/web/activity_pub/mrf/hellthread_policy_test.exs index eb6ee4d04..916b95692 100644 --- a/test/web/activity_pub/mrf/hellthread_policy_test.exs +++ b/test/web/activity_pub/mrf/hellthread_policy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicyTest do @@ -26,6 +26,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicyTest do [user: user, message: message] end + clear_config(:mrf_hellthread) + describe "reject" do test "rejects the message if the recipient count is above reject_threshold", %{ message: message diff --git a/test/web/activity_pub/mrf/keyword_policy_test.exs b/test/web/activity_pub/mrf/keyword_policy_test.exs index 602892a37..18242a889 100644 --- a/test/web/activity_pub/mrf/keyword_policy_test.exs +++ b/test/web/activity_pub/mrf/keyword_policy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do @@ -7,6 +7,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do alias Pleroma.Web.ActivityPub.MRF.KeywordPolicy + clear_config(:mrf_keyword) + setup do Pleroma.Config.put([:mrf_keyword], %{reject: [], federated_timeline_removal: [], replace: []}) end diff --git a/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs b/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs index 372e789be..313d59a66 100644 --- a/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs +++ b/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs @@ -1,11 +1,12 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicyTest do use Pleroma.DataCase alias Pleroma.HTTP + alias Pleroma.Tests.ObanHelpers alias Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy import Mock @@ -24,6 +25,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicyTest do test "it prefetches media proxy URIs" do with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do MediaProxyWarmingPolicy.filter(@message) + + ObanHelpers.perform_all() + # Performing jobs which has been just enqueued + ObanHelpers.perform_all() + assert called(HTTP.get(:_, :_, :_)) end end diff --git a/test/web/activity_pub/mrf/mention_policy_test.exs b/test/web/activity_pub/mrf/mention_policy_test.exs index 9fd9c31df..08f7be542 100644 --- a/test/web/activity_pub/mrf/mention_policy_test.exs +++ b/test/web/activity_pub/mrf/mention_policy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicyTest do @@ -7,6 +7,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicyTest do alias Pleroma.Web.ActivityPub.MRF.MentionPolicy + clear_config(:mrf_mention) + test "pass filter if allow list is empty" do Pleroma.Config.delete([:mrf_mention]) diff --git a/test/web/activity_pub/mrf/normalize_markup_test.exs b/test/web/activity_pub/mrf/normalize_markup_test.exs index 3916a1f35..0207be56b 100644 --- a/test/web/activity_pub/mrf/normalize_markup_test.exs +++ b/test/web/activity_pub/mrf/normalize_markup_test.exs @@ -20,11 +20,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkupTest do expected = """ <b>this is in bold</b> <p>this is a paragraph</p> - this is a linebreak<br /> - this is a link with allowed "rel" attribute: <a href="http://example.com/" rel="tag">example.com</a> - this is a link with not allowed "rel" attribute: <a href="http://example.com/">example.com</a> - this is an image: <img src="http://example.com/image.jpg" /><br /> - alert('hacked') + this is a linebreak<br/> + this is a link with allowed "rel" attribute: <a href="http://example.com/" rel="tag">example.com</a> + this is a link with not allowed "rel" attribute: <a href="http://example.com/">example.com</a> + this is an image: <img src="http://example.com/image.jpg"/><br/> + alert('hacked') """ message = %{"type" => "Create", "object" => %{"content" => @html_sample}} diff --git a/test/web/activity_pub/mrf/object_age_policy_test.exs b/test/web/activity_pub/mrf/object_age_policy_test.exs new file mode 100644 index 000000000..643609da4 --- /dev/null +++ b/test/web/activity_pub/mrf/object_age_policy_test.exs @@ -0,0 +1,105 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicyTest do + use Pleroma.DataCase + alias Pleroma.Config + alias Pleroma.User + alias Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy + alias Pleroma.Web.ActivityPub.Visibility + + clear_config([:mrf_object_age]) do + Config.put(:mrf_object_age, + threshold: 172_800, + actions: [:delist, :strip_followers] + ) + end + + setup_all do + Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + + describe "with reject action" do + test "it rejects an old post" do + Config.put([:mrf_object_age, :actions], [:reject]) + + data = + File.read!("test/fixtures/mastodon-post-activity.json") + |> Poison.decode!() + + {:reject, _} = ObjectAgePolicy.filter(data) + end + + test "it allows a new post" do + Config.put([:mrf_object_age, :actions], [:reject]) + + data = + File.read!("test/fixtures/mastodon-post-activity.json") + |> Poison.decode!() + |> Map.put("published", DateTime.utc_now() |> DateTime.to_iso8601()) + + {:ok, _} = ObjectAgePolicy.filter(data) + end + end + + describe "with delist action" do + test "it delists an old post" do + Config.put([:mrf_object_age, :actions], [:delist]) + + data = + File.read!("test/fixtures/mastodon-post-activity.json") + |> Poison.decode!() + + {:ok, _u} = User.get_or_fetch_by_ap_id(data["actor"]) + + {:ok, data} = ObjectAgePolicy.filter(data) + + assert Visibility.get_visibility(%{data: data}) == "unlisted" + end + + test "it allows a new post" do + Config.put([:mrf_object_age, :actions], [:delist]) + + data = + File.read!("test/fixtures/mastodon-post-activity.json") + |> Poison.decode!() + |> Map.put("published", DateTime.utc_now() |> DateTime.to_iso8601()) + + {:ok, _user} = User.get_or_fetch_by_ap_id(data["actor"]) + + {:ok, ^data} = ObjectAgePolicy.filter(data) + end + end + + describe "with strip_followers action" do + test "it strips followers collections from an old post" do + Config.put([:mrf_object_age, :actions], [:strip_followers]) + + data = + File.read!("test/fixtures/mastodon-post-activity.json") + |> Poison.decode!() + + {:ok, user} = User.get_or_fetch_by_ap_id(data["actor"]) + + {:ok, data} = ObjectAgePolicy.filter(data) + + refute user.follower_address in data["to"] + refute user.follower_address in data["cc"] + end + + test "it allows a new post" do + Config.put([:mrf_object_age, :actions], [:strip_followers]) + + data = + File.read!("test/fixtures/mastodon-post-activity.json") + |> Poison.decode!() + |> Map.put("published", DateTime.utc_now() |> DateTime.to_iso8601()) + + {:ok, _u} = User.get_or_fetch_by_ap_id(data["actor"]) + + {:ok, ^data} = ObjectAgePolicy.filter(data) + end + end +end diff --git a/test/web/activity_pub/mrf/simple_policy_test.exs b/test/web/activity_pub/mrf/simple_policy_test.exs index 7203b27da..df0f223f8 100644 --- a/test/web/activity_pub/mrf/simple_policy_test.exs +++ b/test/web/activity_pub/mrf/simple_policy_test.exs @@ -236,7 +236,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do assert SimplePolicy.filter(remote_message) == {:ok, remote_message} end - test "has a matching host" do + test "activity has a matching host" do Config.put([:mrf_simple, :reject], ["remote.instance"]) remote_message = build_remote_message() @@ -244,13 +244,21 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do assert SimplePolicy.filter(remote_message) == {:reject, nil} end - test "match with wildcard domain" do + test "activity matches with wildcard domain" do Config.put([:mrf_simple, :reject], ["*.remote.instance"]) remote_message = build_remote_message() assert SimplePolicy.filter(remote_message) == {:reject, nil} end + + test "actor has a matching host" do + Config.put([:mrf_simple, :reject], ["remote.instance"]) + + remote_user = build_remote_user() + + assert SimplePolicy.filter(remote_user) == {:reject, nil} + end end describe "when :accept" do @@ -264,7 +272,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do assert SimplePolicy.filter(remote_message) == {:ok, remote_message} end - test "is not empty but it doesn't have a matching host" do + test "is not empty but activity doesn't have a matching host" do Config.put([:mrf_simple, :accept], ["non.matching.remote"]) local_message = build_local_message() @@ -274,7 +282,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do assert SimplePolicy.filter(remote_message) == {:reject, nil} end - test "has a matching host" do + test "activity has a matching host" do Config.put([:mrf_simple, :accept], ["remote.instance"]) local_message = build_local_message() @@ -284,7 +292,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do assert SimplePolicy.filter(remote_message) == {:ok, remote_message} end - test "match with wildcard domain" do + test "activity matches with wildcard domain" do Config.put([:mrf_simple, :accept], ["*.remote.instance"]) local_message = build_local_message() @@ -293,6 +301,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do assert SimplePolicy.filter(local_message) == {:ok, local_message} assert SimplePolicy.filter(remote_message) == {:ok, remote_message} end + + test "actor has a matching host" do + Config.put([:mrf_simple, :accept], ["remote.instance"]) + + remote_user = build_remote_user() + + assert SimplePolicy.filter(remote_user) == {:ok, remote_user} + end end describe "when :avatar_removal" do diff --git a/test/web/activity_pub/mrf/subchain_policy_test.exs b/test/web/activity_pub/mrf/subchain_policy_test.exs index f7cbcad48..221b8958e 100644 --- a/test/web/activity_pub/mrf/subchain_policy_test.exs +++ b/test/web/activity_pub/mrf/subchain_policy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicyTest do @@ -14,6 +14,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicyTest do "object" => %{"content" => "hi"} } + clear_config([:mrf_subchain, :match_actor]) + test "it matches and processes subchains when the actor matches a configured target" do Pleroma.Config.put([:mrf_subchain, :match_actor], %{ ~r/^https:\/\/banned.com/s => [DropPolicy] diff --git a/test/web/activity_pub/mrf/tag_policy_test.exs b/test/web/activity_pub/mrf/tag_policy_test.exs index 4aa35311e..e7793641a 100644 --- a/test/web/activity_pub/mrf/tag_policy_test.exs +++ b/test/web/activity_pub/mrf/tag_policy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.TagPolicyTest do diff --git a/test/web/activity_pub/mrf/user_allowlist_policy_test.exs b/test/web/activity_pub/mrf/user_allowlist_policy_test.exs index 72084c0fd..87c9e1b29 100644 --- a/test/web/activity_pub/mrf/user_allowlist_policy_test.exs +++ b/test/web/activity_pub/mrf/user_allowlist_policy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicyTest do use Pleroma.DataCase diff --git a/test/web/activity_pub/mrf/vocabulary_policy_test.exs b/test/web/activity_pub/mrf/vocabulary_policy_test.exs index 38309f9f1..d9207b095 100644 --- a/test/web/activity_pub/mrf/vocabulary_policy_test.exs +++ b/test/web/activity_pub/mrf/vocabulary_policy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicyTest do diff --git a/test/web/activity_pub/publisher_test.exs b/test/web/activity_pub/publisher_test.exs index 36a39c84c..3404848d4 100644 --- a/test/web/activity_pub/publisher_test.exs +++ b/test/web/activity_pub/publisher_test.exs @@ -1,17 +1,20 @@ # Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.PublisherTest do - use Pleroma.DataCase + use Pleroma.Web.ConnCase + import ExUnit.CaptureLog import Pleroma.Factory import Tesla.Mock import Mock alias Pleroma.Activity alias Pleroma.Instances + alias Pleroma.Object alias Pleroma.Web.ActivityPub.Publisher + alias Pleroma.Web.CommonAPI @as_public "https://www.w3.org/ns/activitystreams#Public" @@ -20,11 +23,32 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do :ok end + describe "gather_webfinger_links/1" do + test "it returns links" do + user = insert(:user) + + expected_links = [ + %{"href" => user.ap_id, "rel" => "self", "type" => "application/activity+json"}, + %{ + "href" => user.ap_id, + "rel" => "self", + "type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" + }, + %{ + "rel" => "http://ostatus.org/schema/1.0/subscribe", + "template" => "#{Pleroma.Web.base_url()}/ostatus_subscribe?acct={uri}" + } + ] + + assert expected_links == Publisher.gather_webfinger_links(user) + end + end + describe "determine_inbox/2" do test "it returns sharedInbox for messages involving as:Public in to" do user = insert(:user, %{ - info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}} + source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}} }) activity = %Activity{ @@ -37,7 +61,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do test "it returns sharedInbox for messages involving as:Public in cc" do user = insert(:user, %{ - info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}} + source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}} }) activity = %Activity{ @@ -50,7 +74,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do test "it returns sharedInbox for messages involving multiple recipients in to" do user = insert(:user, %{ - info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}} + source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}} }) user_two = insert(:user) @@ -66,7 +90,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do test "it returns sharedInbox for messages involving multiple recipients in cc" do user = insert(:user, %{ - info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}} + source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}} }) user_two = insert(:user) @@ -81,14 +105,12 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do test "it returns sharedInbox for messages involving multiple recipients in total" do user = - insert(:user, %{ - info: %{ - source_data: %{ - "inbox" => "http://example.com/personal-inbox", - "endpoints" => %{"sharedInbox" => "http://example.com/inbox"} - } + insert(:user, + source_data: %{ + "inbox" => "http://example.com/personal-inbox", + "endpoints" => %{"sharedInbox" => "http://example.com/inbox"} } - }) + ) user_two = insert(:user) @@ -101,14 +123,12 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do test "it returns inbox for messages involving single recipients in total" do user = - insert(:user, %{ - info: %{ - source_data: %{ - "inbox" => "http://example.com/personal-inbox", - "endpoints" => %{"sharedInbox" => "http://example.com/inbox"} - } + insert(:user, + source_data: %{ + "inbox" => "http://example.com/personal-inbox", + "endpoints" => %{"sharedInbox" => "http://example.com/inbox"} } - }) + ) activity = %Activity{ data: %{"to" => [user.ap_id], "cc" => []} @@ -188,7 +208,10 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do actor = insert(:user) inbox = "http://connrefused.site/users/nick1/inbox" - assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) + assert capture_log(fn -> + assert {:error, _} = + Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) + end) =~ "connrefused" assert called(Instances.set_unreachable(inbox)) end @@ -212,14 +235,16 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do actor = insert(:user) inbox = "http://connrefused.site/users/nick1/inbox" - assert {:error, _} = - Publisher.publish_one(%{ - inbox: inbox, - json: "{}", - actor: actor, - id: 1, - unreachable_since: NaiveDateTime.utc_now() - }) + assert capture_log(fn -> + assert {:error, _} = + Publisher.publish_one(%{ + inbox: inbox, + json: "{}", + actor: actor, + id: 1, + unreachable_since: NaiveDateTime.utc_now() + }) + end) =~ "connrefused" refute called(Instances.set_unreachable(inbox)) end @@ -233,10 +258,8 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do follower = insert(:user, local: false, - info: %{ - ap_enabled: true, - source_data: %{"inbox" => "https://domain.com/users/nick1/inbox"} - } + source_data: %{"inbox" => "https://domain.com/users/nick1/inbox"}, + ap_enabled: true ) actor = insert(:user, follower_address: follower.ap_id) @@ -257,10 +280,70 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do assert called( Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{ inbox: "https://domain.com/users/nick1/inbox", - actor: actor, + actor_id: actor.id, id: note_activity.data["id"] }) ) end + + test_with_mock "publishes a delete activity to peers who signed fetch requests to the create acitvity/object.", + Pleroma.Web.Federator.Publisher, + [:passthrough], + [] do + fetcher = + insert(:user, + local: false, + source_data: %{"inbox" => "https://domain.com/users/nick1/inbox"}, + ap_enabled: true + ) + + another_fetcher = + insert(:user, + local: false, + source_data: %{"inbox" => "https://domain2.com/users/nick1/inbox"}, + ap_enabled: true + ) + + actor = insert(:user) + + note_activity = insert(:note_activity, user: actor) + object = Object.normalize(note_activity) + + activity_path = String.trim_leading(note_activity.data["id"], Pleroma.Web.Endpoint.url()) + object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url()) + + build_conn() + |> put_req_header("accept", "application/activity+json") + |> assign(:user, fetcher) + |> get(object_path) + |> json_response(200) + + build_conn() + |> put_req_header("accept", "application/activity+json") + |> assign(:user, another_fetcher) + |> get(activity_path) + |> json_response(200) + + {:ok, delete} = CommonAPI.delete(note_activity.id, actor) + + res = Publisher.publish(actor, delete) + assert res == :ok + + assert called( + Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{ + inbox: "https://domain.com/users/nick1/inbox", + actor_id: actor.id, + id: delete.data["id"] + }) + ) + + assert called( + Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{ + inbox: "https://domain2.com/users/nick1/inbox", + actor_id: actor.id, + id: delete.data["id"] + }) + ) + end end end diff --git a/test/web/activity_pub/relay_test.exs b/test/web/activity_pub/relay_test.exs index 4f7d592a6..e3115dcd8 100644 --- a/test/web/activity_pub/relay_test.exs +++ b/test/web/activity_pub/relay_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.RelayTest do @@ -7,9 +7,11 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do alias Pleroma.Activity alias Pleroma.Object + alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Relay + import ExUnit.CaptureLog import Pleroma.Factory import Mock @@ -18,9 +20,16 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do assert user.ap_id == "#{Pleroma.Web.Endpoint.url()}/relay" end + test "relay actor is invisible" do + user = Relay.get_actor() + assert User.invisible?(user) + end + describe "follow/1" do test "returns errors when user not found" do - assert Relay.follow("test-ap-id") == {:error, "Could not fetch by AP id"} + assert capture_log(fn -> + {:error, _} = Relay.follow("test-ap-id") + end) =~ "Could not decode user at fetch" end test "returns activity" do @@ -37,7 +46,9 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do describe "unfollow/1" do test "returns errors when user not found" do - assert Relay.unfollow("test-ap-id") == {:error, "Could not fetch by AP id"} + assert capture_log(fn -> + {:error, _} = Relay.unfollow("test-ap-id") + end) =~ "Could not decode user at fetch" end test "returns activity" do @@ -45,14 +56,14 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do service_actor = Relay.get_actor() ActivityPub.follow(service_actor, user) Pleroma.User.follow(service_actor, user) - assert "#{user.ap_id}/followers" in refresh_record(service_actor).following + assert "#{user.ap_id}/followers" in User.following(service_actor) assert {:ok, %Activity{} = activity} = Relay.unfollow(user.ap_id) assert activity.actor == "#{Pleroma.Web.Endpoint.url()}/relay" assert user.ap_id in activity.recipients assert activity.data["type"] == "Undo" assert activity.data["actor"] == service_actor.ap_id assert activity.data["to"] == [user.ap_id] - refute "#{user.ap_id}/followers" in refresh_record(service_actor).following + refute "#{user.ap_id}/followers" in User.following(service_actor) end end @@ -78,7 +89,9 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do } ) - assert Relay.publish(activity) == {:error, nil} + assert capture_log(fn -> + assert Relay.publish(activity) == {:error, nil} + end) =~ "[error] error: nil" end test_with_mock "returns announce activity and publish to federate", @@ -92,7 +105,7 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do assert activity.data["type"] == "Announce" assert activity.data["actor"] == service_actor.ap_id assert activity.data["object"] == obj.data["id"] - assert called(Pleroma.Web.Federator.publish(activity, 5)) + assert called(Pleroma.Web.Federator.publish(activity)) end test_with_mock "returns announce activity and not publish to federate", @@ -106,7 +119,7 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do assert activity.data["type"] == "Announce" assert activity.data["actor"] == service_actor.ap_id assert activity.data["object"] == obj.data["id"] - refute called(Pleroma.Web.Federator.publish(activity, 5)) + refute called(Pleroma.Web.Federator.publish(activity)) end end end diff --git a/test/web/activity_pub/transmogrifier/follow_handling_test.exs b/test/web/activity_pub/transmogrifier/follow_handling_test.exs index fe89f7cb0..c3d3f9830 100644 --- a/test/web/activity_pub/transmogrifier/follow_handling_test.exs +++ b/test/web/activity_pub/transmogrifier/follow_handling_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do @@ -19,6 +19,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do end describe "handle_incoming" do + clear_config([:user, :deny_follow_blocked]) + test "it works for osada follow request" do user = insert(:user) @@ -58,7 +60,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do end test "with locked accounts, it does not create a follow or an accept" do - user = insert(:user, info: %{locked: true}) + user = insert(:user, locked: true) data = File.read!("test/fixtures/mastodon-follow-activity.json") @@ -78,7 +80,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do ) |> Repo.all() - assert length(accepts) == 0 + assert Enum.empty?(accepts) end test "it works for follow requests when you are already followed, creating a new accept activity" do @@ -128,7 +130,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do user = insert(:user) {:ok, target} = User.get_or_fetch("http://mastodon.example.org/users/admin") - {:ok, user} = User.block(user, target) + {:ok, _user_relationship} = User.block(user, target) data = File.read!("test/fixtures/mastodon-follow-activity.json") diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 300264ea9..efbca82f6 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -1,19 +1,20 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do + use Oban.Testing, repo: Pleroma.Repo use Pleroma.DataCase + alias Pleroma.Activity alias Pleroma.Object alias Pleroma.Object.Fetcher - alias Pleroma.Repo + alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.AdminAPI.AccountView alias Pleroma.Web.CommonAPI - alias Pleroma.Web.OStatus - alias Pleroma.Web.Websub.WebsubClientSubscription import Mock import Pleroma.Factory @@ -40,7 +41,8 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert activity == returned_activity end - test "it fetches replied-to activities if we don't have them" do + @tag capture_log: true + test "it fetches reply-to activities if we don't have them" do data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() @@ -61,7 +63,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" end - test "it does not fetch replied-to activities beyond max_replies_depth" do + test "it does not fetch reply-to activities beyond max replies depth limit" do data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() @@ -73,7 +75,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do data = Map.put(data, "object", object) with_mock Pleroma.Web.Federator, - allowed_incoming_reply_depth?: fn _ -> false end do + allowed_thread_distance?: fn _ -> false end do {:ok, returned_activity} = Transmogrifier.handle_incoming(data) returned_object = Object.normalize(returned_activity, false) @@ -147,7 +149,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do user = User.get_cached_by_ap_id(object_data["actor"]) - assert user.info.note_count == 1 + assert user.note_count == 1 end test "it works for incoming notices with hashtags" do @@ -176,6 +178,35 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do end) end + test "it works for incoming listens" do + data = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "to" => ["https://www.w3.org/ns/activitystreams#Public"], + "cc" => [], + "type" => "Listen", + "id" => "http://mastodon.example.org/users/admin/listens/1234/activity", + "actor" => "http://mastodon.example.org/users/admin", + "object" => %{ + "type" => "Audio", + "id" => "http://mastodon.example.org/users/admin/listens/1234", + "attributedTo" => "http://mastodon.example.org/users/admin", + "title" => "lain radio episode 1", + "artist" => "lain", + "album" => "lain radio", + "length" => 180_000 + } + } + + {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) + + object = Object.normalize(activity) + + assert object.data["title"] == "lain radio episode 1" + assert object.data["artist"] == "lain" + assert object.data["album"] == "lain radio" + assert object.data["length"] == 180_000 + end + test "it rewrites Note votes to Answers and increments vote counters on question activities" do user = insert(:user) @@ -311,6 +342,99 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert data["object"] == activity.data["object"] end + test "it works for incoming misskey likes, turning them into EmojiReacts" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) + + data = + File.read!("test/fixtures/misskey-like.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == data["actor"] + assert data["type"] == "EmojiReact" + assert data["id"] == data["id"] + assert data["object"] == activity.data["object"] + assert data["content"] == "🍮" + end + + test "it works for incoming misskey likes that contain unicode emojis, turning them into EmojiReacts" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) + + data = + File.read!("test/fixtures/misskey-like.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + |> Map.put("_misskey_reaction", "⭐") + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == data["actor"] + assert data["type"] == "EmojiReact" + assert data["id"] == data["id"] + assert data["object"] == activity.data["object"] + assert data["content"] == "⭐" + end + + test "it works for incoming emoji reactions" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) + + data = + File.read!("test/fixtures/emoji-reaction.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == "http://mastodon.example.org/users/admin" + assert data["type"] == "EmojiReact" + assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2" + assert data["object"] == activity.data["object"] + assert data["content"] == "👌" + end + + test "it reject invalid emoji reactions" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) + + data = + File.read!("test/fixtures/emoji-reaction-too-long.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + assert :error = Transmogrifier.handle_incoming(data) + + data = + File.read!("test/fixtures/emoji-reaction-no-emoji.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + assert :error = Transmogrifier.handle_incoming(data) + end + + test "it works for incoming emoji reaction undos" do + user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) + {:ok, reaction_activity, _object} = CommonAPI.react_with_emoji(activity.id, user, "👌") + + data = + File.read!("test/fixtures/mastodon-undo-like.json") + |> Poison.decode!() + |> Map.put("object", reaction_activity.data["id"]) + |> Map.put("actor", user.ap_id) + + {:ok, activity} = Transmogrifier.handle_incoming(data) + + assert activity.actor == user.ap_id + assert activity.data["id"] == data["id"] + assert activity.data["type"] == "Undo" + end + test "it returns an error for incoming unlikes wihout a like activity" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"}) @@ -412,6 +536,34 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert Activity.get_create_by_object_ap_id(data["object"]).id == activity.id end + test "it works for incoming announces with an inlined activity" do + data = + File.read!("test/fixtures/mastodon-announce-private.json") + |> Poison.decode!() + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == "http://mastodon.example.org/users/admin" + assert data["type"] == "Announce" + + assert data["id"] == + "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity" + + object = Object.normalize(data["object"]) + + assert object.data["id"] == "http://mastodon.example.org/@admin/99541947525187368" + assert object.data["content"] == "this is a private toot" + end + + @tag capture_log: true + test "it rejects incoming announces with an inlined activity from another origin" do + data = + File.read!("test/fixtures/bogus-mastodon-announce.json") + |> Poison.decode!() + + assert :error = Transmogrifier.handle_incoming(data) + end + test "it does not clobber the addressing on announce activities" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"}) @@ -498,6 +650,20 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do refute Map.has_key?(object.data, "likes") end + test "it strips internal reactions" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) + {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, user, "📢") + + %{object: object} = Activity.get_by_id_with_object(activity.id) + assert Map.has_key?(object.data, "reactions") + assert Map.has_key?(object.data, "reaction_count") + + object_data = Transmogrifier.strip_internal_fields(object.data) + refute Map.has_key?(object_data, "reactions") + refute Map.has_key?(object_data, "reaction_count") + end + test "it works for incoming update activities" do data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() @@ -528,7 +694,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do } ] - assert user.info.banner["url"] == [ + assert user.banner["url"] == [ %{ "href" => "https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png" @@ -538,6 +704,37 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert user.bio == "<p>Some bio</p>" end + test "it works with alsoKnownAs" do + {:ok, %Activity{data: %{"actor" => actor}}} = + "test/fixtures/mastodon-post-activity.json" + |> File.read!() + |> Poison.decode!() + |> Transmogrifier.handle_incoming() + + assert User.get_cached_by_ap_id(actor).also_known_as == ["http://example.org/users/foo"] + + {:ok, _activity} = + "test/fixtures/mastodon-update.json" + |> File.read!() + |> Poison.decode!() + |> Map.put("actor", actor) + |> Map.update!("object", fn object -> + object + |> Map.put("actor", actor) + |> Map.put("id", actor) + |> Map.put("alsoKnownAs", [ + "http://mastodon.example.org/users/foo", + "http://example.org/users/bar" + ]) + end) + |> Transmogrifier.handle_incoming() + + assert User.get_cached_by_ap_id(actor).also_known_as == [ + "http://mastodon.example.org/users/foo", + "http://example.org/users/bar" + ] + end + test "it works with custom profile fields" do {:ok, activity} = "test/fixtures/mastodon-post-activity.json" @@ -547,7 +744,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do user = User.get_cached_by_ap_id(activity.actor) - assert User.Info.fields(user.info) == [ + assert User.fields(user) == [ %{"name" => "foo", "value" => "bar"}, %{"name" => "foo1", "value" => "bar1"} ] @@ -568,7 +765,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do user = User.get_cached_by_ap_id(user.ap_id) - assert User.Info.fields(user.info) == [ + assert User.fields(user) == [ %{"name" => "foo", "value" => "updated"}, %{"name" => "foo1", "value" => "updated"} ] @@ -586,7 +783,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do user = User.get_cached_by_ap_id(user.ap_id) - assert User.Info.fields(user.info) == [ + assert User.fields(user) == [ %{"name" => "foo", "value" => "updated"}, %{"name" => "foo1", "value" => "updated"} ] @@ -597,7 +794,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do user = User.get_cached_by_ap_id(user.ap_id) - assert User.Info.fields(user.info) == [] + assert User.fields(user) == [] end test "it works for incoming update activities which lock the account" do @@ -620,11 +817,12 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data) user = User.get_cached_by_ap_id(data["actor"]) - assert user.info.locked == true + assert user.locked == true end test "it works for incoming deletes" do activity = insert(:note_activity) + deleting_user = insert(:user) data = File.read!("test/fixtures/mastodon-delete.json") @@ -637,11 +835,14 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do data = data |> Map.put("object", object) - |> Map.put("actor", activity.data["actor"]) + |> Map.put("actor", deleting_user.ap_id) - {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data) + {:ok, %Activity{actor: actor, local: false, data: %{"id" => id}}} = + Transmogrifier.handle_incoming(data) + assert id == data["id"] refute Activity.get_by_id(activity.id) + assert actor == deleting_user.ap_id end test "it fails for incoming deletes with spoofed origin" do @@ -662,11 +863,12 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert capture_log(fn -> :error = Transmogrifier.handle_incoming(data) end) =~ - "[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, {:error, :nxdomain}}" + "[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, :nxdomain}" assert Activity.get_by_id(activity.id) end + @tag capture_log: true test "it works for incoming user deletes" do %{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin") @@ -675,6 +877,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do |> Poison.decode!() {:ok, _} = Transmogrifier.handle_incoming(data) + ObanHelpers.perform_all() refute User.get_cached_by_ap_id(ap_id) end @@ -687,7 +890,10 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do |> Poison.decode!() |> Map.put("actor", ap_id) - assert :error == Transmogrifier.handle_incoming(data) + assert capture_log(fn -> + assert :error == Transmogrifier.handle_incoming(data) + end) =~ "Object containment failed" + assert User.get_cached_by_ap_id(ap_id) end @@ -745,6 +951,25 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do refute User.following?(User.get_cached_by_ap_id(data["actor"]), user) end + test "it works for incoming follows to locked account" do + pending_follower = insert(:user, ap_id: "http://mastodon.example.org/users/admin") + user = insert(:user, locked: true) + + data = + File.read!("test/fixtures/mastodon-follow-activity.json") + |> Poison.decode!() + |> Map.put("object", user.ap_id) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["type"] == "Follow" + assert data["object"] == user.ap_id + assert data["state"] == "pending" + assert data["actor"] == "http://mastodon.example.org/users/admin" + + assert [^pending_follower] = User.get_follow_requests(user) + end + test "it works for incoming blocks" do user = insert(:user) @@ -847,6 +1072,8 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert activity.data["object"] == follow_activity.data["id"] + assert activity.data["id"] == accept_data["id"] + follower = User.get_cached_by_id(follower.id) assert User.following?(follower, followed) == true @@ -854,7 +1081,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do test "it works for incoming accepts which were orphaned" do follower = insert(:user) - followed = insert(:user, %{info: %User.Info{locked: true}}) + followed = insert(:user, locked: true) {:ok, follow_activity} = ActivityPub.follow(follower, followed) @@ -876,7 +1103,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do test "it works for incoming accepts which are referenced by IRI only" do follower = insert(:user) - followed = insert(:user, %{info: %User.Info{locked: true}}) + followed = insert(:user, locked: true) {:ok, follow_activity} = ActivityPub.follow(follower, followed) @@ -896,7 +1123,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do test "it fails for incoming accepts which cannot be correlated" do follower = insert(:user) - followed = insert(:user, %{info: %User.Info{locked: true}}) + followed = insert(:user, locked: true) accept_data = File.read!("test/fixtures/mastodon-accept-activity.json") @@ -915,7 +1142,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do test "it fails for incoming rejects which cannot be correlated" do follower = insert(:user) - followed = insert(:user, %{info: %User.Info{locked: true}}) + followed = insert(:user, locked: true) accept_data = File.read!("test/fixtures/mastodon-reject-activity.json") @@ -934,7 +1161,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do test "it works for incoming rejects which are orphaned" do follower = insert(:user) - followed = insert(:user, %{info: %User.Info{locked: true}}) + followed = insert(:user, locked: true) {:ok, follower} = User.follow(follower, followed) {:ok, _follow_activity} = ActivityPub.follow(follower, followed) @@ -951,6 +1178,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do {:ok, activity} = Transmogrifier.handle_incoming(reject_data) refute activity.local + assert activity.data["id"] == reject_data["id"] follower = User.get_cached_by_id(follower.id) @@ -959,7 +1187,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do test "it works for incoming rejects which are referenced by IRI only" do follower = insert(:user) - followed = insert(:user, %{info: %User.Info{locked: true}}) + followed = insert(:user, locked: true) {:ok, follower} = User.follow(follower, followed) {:ok, follow_activity} = ActivityPub.follow(follower, followed) @@ -1028,10 +1256,18 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) object = Object.normalize(activity) + note_obj = %{ + "type" => "Note", + "id" => activity.data["id"], + "content" => "test post", + "published" => object.data["published"], + "actor" => AccountView.render("show.json", %{user: user}) + } + message = %{ "@context" => "https://www.w3.org/ns/activitystreams", "cc" => [user.ap_id], - "object" => [user.ap_id, object.data["id"]], + "object" => [user.ap_id, activity.data["id"]], "type" => "Flag", "content" => "blocked AND reported!!!", "actor" => other_user.ap_id @@ -1039,14 +1275,190 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert {:ok, activity} = Transmogrifier.handle_incoming(message) - assert activity.data["object"] == [user.ap_id, object.data["id"]] + assert activity.data["object"] == [user.ap_id, note_obj] assert activity.data["content"] == "blocked AND reported!!!" assert activity.data["actor"] == other_user.ap_id assert activity.data["cc"] == [user.ap_id] end + + test "it correctly processes messages with non-array to field" do + user = insert(:user) + + message = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "to" => "https://www.w3.org/ns/activitystreams#Public", + "type" => "Create", + "object" => %{ + "content" => "blah blah blah", + "type" => "Note", + "attributedTo" => user.ap_id, + "inReplyTo" => nil + }, + "actor" => user.ap_id + } + + assert {:ok, activity} = Transmogrifier.handle_incoming(message) + + assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"] + end + + test "it correctly processes messages with non-array cc field" do + user = insert(:user) + + message = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "to" => user.follower_address, + "cc" => "https://www.w3.org/ns/activitystreams#Public", + "type" => "Create", + "object" => %{ + "content" => "blah blah blah", + "type" => "Note", + "attributedTo" => user.ap_id, + "inReplyTo" => nil + }, + "actor" => user.ap_id + } + + assert {:ok, activity} = Transmogrifier.handle_incoming(message) + + assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"] + assert [user.follower_address] == activity.data["to"] + end + + test "it accepts Move activities" do + old_user = insert(:user) + new_user = insert(:user) + + message = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "type" => "Move", + "actor" => old_user.ap_id, + "object" => old_user.ap_id, + "target" => new_user.ap_id + } + + assert :error = Transmogrifier.handle_incoming(message) + + {:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]}) + + assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message) + assert activity.actor == old_user.ap_id + assert activity.data["actor"] == old_user.ap_id + assert activity.data["object"] == old_user.ap_id + assert activity.data["target"] == new_user.ap_id + assert activity.data["type"] == "Move" + end + end + + describe "`handle_incoming/2`, Mastodon format `replies` handling" do + clear_config([:activitypub, :note_replies_output_limit]) do + Pleroma.Config.put([:activitypub, :note_replies_output_limit], 5) + end + + clear_config([:instance, :federation_incoming_replies_max_depth]) + + setup do + data = + "test/fixtures/mastodon-post-activity.json" + |> File.read!() + |> Poison.decode!() + + items = get_in(data, ["object", "replies", "first", "items"]) + assert length(items) > 0 + + %{data: data, items: items} + end + + test "schedules background fetching of `replies` items if max thread depth limit allows", %{ + data: data, + items: items + } do + Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10) + + {:ok, _activity} = Transmogrifier.handle_incoming(data) + + for id <- items do + job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1} + assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args) + end + end + + test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows", + %{data: data} do + Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0) + + {:ok, _activity} = Transmogrifier.handle_incoming(data) + + assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == [] + end + end + + describe "`handle_incoming/2`, Pleroma format `replies` handling" do + clear_config([:activitypub, :note_replies_output_limit]) do + Pleroma.Config.put([:activitypub, :note_replies_output_limit], 5) + end + + clear_config([:instance, :federation_incoming_replies_max_depth]) + + setup do + user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "post1"}) + + {:ok, reply1} = + CommonAPI.post(user, %{"status" => "reply1", "in_reply_to_status_id" => activity.id}) + + {:ok, reply2} = + CommonAPI.post(user, %{"status" => "reply2", "in_reply_to_status_id" => activity.id}) + + replies_uris = Enum.map([reply1, reply2], fn a -> a.object.data["id"] end) + + {:ok, federation_output} = Transmogrifier.prepare_outgoing(activity.data) + + Repo.delete(activity.object) + Repo.delete(activity) + + %{federation_output: federation_output, replies_uris: replies_uris} + end + + test "schedules background fetching of `replies` items if max thread depth limit allows", %{ + federation_output: federation_output, + replies_uris: replies_uris + } do + Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 1) + + {:ok, _activity} = Transmogrifier.handle_incoming(federation_output) + + for id <- replies_uris do + job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1} + assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args) + end + end + + test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows", + %{federation_output: federation_output} do + Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0) + + {:ok, _activity} = Transmogrifier.handle_incoming(federation_output) + + assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == [] + end end describe "prepare outgoing" do + test "it inlines private announced objects" do + user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "hey", "visibility" => "private"}) + + {:ok, announce_activity, _} = CommonAPI.repeat(activity.id, user) + + {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data) + + assert modified["object"]["content"] == "hey" + assert modified["object"]["actor"] == modified["object"]["attributedTo"] + end + test "it turns mentions into tags" do user = insert(:user) other_user = insert(:user) @@ -1103,32 +1515,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert modified["object"]["actor"] == modified["object"]["attributedTo"] end - test "it translates ostatus IDs to external URLs" do - incoming = File.read!("test/fixtures/incoming_note_activity.xml") - {:ok, [referent_activity]} = OStatus.handle_incoming(incoming) - - user = insert(:user) - - {:ok, activity, _} = CommonAPI.favorite(referent_activity.id, user) - {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) - - assert modified["object"] == "http://gs.example.org:4040/index.php/notice/29" - end - - test "it translates ostatus reply_to IDs to external URLs" do - incoming = File.read!("test/fixtures/incoming_note_activity.xml") - {:ok, [referred_activity]} = OStatus.handle_incoming(incoming) - - user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{"status" => "HI!", "in_reply_to_status_id" => referred_activity.id}) - - {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) - - assert modified["object"]["inReplyTo"] == "http://gs.example.org:4040/index.php/notice/29" - end - test "it strips internal hashtag data" do user = insert(:user) @@ -1215,6 +1601,20 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert is_nil(modified["bcc"]) end + + test "it can handle Listen activities" do + listen_activity = insert(:listen) + + {:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data) + + assert modified["type"] == "Listen" + + user = insert(:user) + + {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"}) + + {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data) + end end describe "user upgrade" do @@ -1227,23 +1627,26 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"}) }) - user_two = insert(:user, %{following: [user.follower_address]}) + user_two = insert(:user) + Pleroma.FollowingRelationship.follow(user_two, user, "accept") {:ok, activity} = CommonAPI.post(user, %{"status" => "test"}) {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"}) assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients user = User.get_cached_by_id(user.id) - assert user.info.note_count == 1 + assert user.note_count == 1 {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye") - assert user.info.ap_enabled - assert user.info.note_count == 1 + ObanHelpers.perform_all() + + assert user.ap_enabled + assert user.note_count == 1 assert user.follower_address == "https://niu.moe/users/rye/followers" assert user.following_address == "https://niu.moe/users/rye/following" user = User.get_cached_by_id(user.id) - assert user.info.note_count == 1 + assert user.note_count == 1 activity = Activity.get_by_id(activity.id) assert user.follower_address in activity.recipients @@ -1264,7 +1667,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png" } ] - } = user.info.banner + } = user.banner refute "..." in activity.recipients @@ -1272,23 +1675,8 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do refute user.follower_address in unrelated_activity.recipients user_two = User.get_cached_by_id(user_two.id) - assert user.follower_address in user_two.following - refute "..." in user_two.following - end - end - - describe "maybe_retire_websub" do - test "it deletes all websub client subscripitions with the user as topic" do - subscription = %WebsubClientSubscription{topic: "https://niu.moe/users/rye.atom"} - {:ok, ws} = Repo.insert(subscription) - - subscription = %WebsubClientSubscription{topic: "https://niu.moe/users/pasty.atom"} - {:ok, ws2} = Repo.insert(subscription) - - Transmogrifier.maybe_retire_websub("https://niu.moe/users/rye") - - refute Repo.get(WebsubClientSubscription, ws.id) - assert Repo.get(WebsubClientSubscription, ws2.id) + assert User.following?(user_two, user) + refute "..." in User.following(user_two) end end @@ -1314,7 +1702,9 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do "type" => "Announce" } - :error = Transmogrifier.handle_incoming(data) + assert capture_log(fn -> + :error = Transmogrifier.handle_incoming(data) + end) =~ "Object containment failed" end test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do @@ -1327,7 +1717,9 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do "type" => "Announce" } - :error = Transmogrifier.handle_incoming(data) + assert capture_log(fn -> + :error = Transmogrifier.handle_incoming(data) + end) =~ "Object containment failed" end test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do @@ -1340,7 +1732,9 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do "type" => "Announce" } - :error = Transmogrifier.handle_incoming(data) + assert capture_log(fn -> + :error = Transmogrifier.handle_incoming(data) + end) =~ "Object containment failed" end end @@ -1478,4 +1872,320 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do refute recipient.follower_address in fixed_object["to"] end end + + describe "fix_summary/1" do + test "returns fixed object" do + assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""} + assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"} + assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""} + end + end + + describe "fix_in_reply_to/2" do + clear_config([:instance, :federation_incoming_replies_max_depth]) + + setup do + data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json")) + [data: data] + end + + test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do + assert Transmogrifier.fix_in_reply_to(data) == data + end + + test "returns object with inReplyToAtomUri when denied incoming reply", %{data: data} do + Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0) + + object_with_reply = + Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873") + + modified_object = Transmogrifier.fix_in_reply_to(object_with_reply) + assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873" + assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" + + object_with_reply = + Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"}) + + modified_object = Transmogrifier.fix_in_reply_to(object_with_reply) + assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"} + assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" + + object_with_reply = + Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"]) + + modified_object = Transmogrifier.fix_in_reply_to(object_with_reply) + assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"] + assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" + + object_with_reply = Map.put(data["object"], "inReplyTo", []) + modified_object = Transmogrifier.fix_in_reply_to(object_with_reply) + assert modified_object["inReplyTo"] == [] + assert modified_object["inReplyToAtomUri"] == "" + end + + @tag capture_log: true + test "returns modified object when allowed incoming reply", %{data: data} do + object_with_reply = + Map.put( + data["object"], + "inReplyTo", + "https://shitposter.club/notice/2827873" + ) + + Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5) + modified_object = Transmogrifier.fix_in_reply_to(object_with_reply) + + assert modified_object["inReplyTo"] == + "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment" + + assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" + + assert modified_object["conversation"] == + "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26" + + assert modified_object["context"] == + "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26" + end + end + + describe "fix_url/1" do + test "fixes data for object when url is map" do + object = %{ + "url" => %{ + "type" => "Link", + "mimeType" => "video/mp4", + "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4" + } + } + + assert Transmogrifier.fix_url(object) == %{ + "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4" + } + end + + test "fixes data for video object" do + object = %{ + "type" => "Video", + "url" => [ + %{ + "type" => "Link", + "mimeType" => "video/mp4", + "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4" + }, + %{ + "type" => "Link", + "mimeType" => "video/mp4", + "href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4" + }, + %{ + "type" => "Link", + "mimeType" => "text/html", + "href" => "https://peertube.-2d4c2d1630e3" + }, + %{ + "type" => "Link", + "mimeType" => "text/html", + "href" => "https://peertube.-2d4c2d16377-42" + } + ] + } + + assert Transmogrifier.fix_url(object) == %{ + "attachment" => [ + %{ + "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4", + "mimeType" => "video/mp4", + "type" => "Link" + } + ], + "type" => "Video", + "url" => "https://peertube.-2d4c2d1630e3" + } + end + + test "fixes url for not Video object" do + object = %{ + "type" => "Text", + "url" => [ + %{ + "type" => "Link", + "mimeType" => "text/html", + "href" => "https://peertube.-2d4c2d1630e3" + }, + %{ + "type" => "Link", + "mimeType" => "text/html", + "href" => "https://peertube.-2d4c2d16377-42" + } + ] + } + + assert Transmogrifier.fix_url(object) == %{ + "type" => "Text", + "url" => "https://peertube.-2d4c2d1630e3" + } + + assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{ + "type" => "Text", + "url" => "" + } + end + + test "retunrs not modified object" do + assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"} + end + end + + describe "get_obj_helper/2" do + test "returns nil when cannot normalize object" do + assert capture_log(fn -> + refute Transmogrifier.get_obj_helper("test-obj-id") + end) =~ "Unsupported URI scheme" + end + + @tag capture_log: true + test "returns {:ok, %Object{}} for success case" do + assert {:ok, %Object{}} = + Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873") + end + end + + describe "fix_attachments/1" do + test "returns not modified object" do + data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json")) + assert Transmogrifier.fix_attachments(data) == data + end + + test "returns modified object when attachment is map" do + assert Transmogrifier.fix_attachments(%{ + "attachment" => %{ + "mediaType" => "video/mp4", + "url" => "https://peertube.moe/stat-480.mp4" + } + }) == %{ + "attachment" => [ + %{ + "mediaType" => "video/mp4", + "url" => [ + %{ + "href" => "https://peertube.moe/stat-480.mp4", + "mediaType" => "video/mp4", + "type" => "Link" + } + ] + } + ] + } + end + + test "returns modified object when attachment is list" do + assert Transmogrifier.fix_attachments(%{ + "attachment" => [ + %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"}, + %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"} + ] + }) == %{ + "attachment" => [ + %{ + "mediaType" => "video/mp4", + "url" => [ + %{ + "href" => "https://pe.er/stat-480.mp4", + "mediaType" => "video/mp4", + "type" => "Link" + } + ] + }, + %{ + "href" => "https://pe.er/stat-480.mp4", + "mediaType" => "video/mp4", + "mimeType" => "video/mp4", + "url" => [ + %{ + "href" => "https://pe.er/stat-480.mp4", + "mediaType" => "video/mp4", + "type" => "Link" + } + ] + } + ] + } + end + end + + describe "fix_emoji/1" do + test "returns not modified object when object not contains tags" do + data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json")) + assert Transmogrifier.fix_emoji(data) == data + end + + test "returns object with emoji when object contains list tags" do + assert Transmogrifier.fix_emoji(%{ + "tag" => [ + %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}, + %{"type" => "Hashtag"} + ] + }) == %{ + "emoji" => %{"bib" => "/test"}, + "tag" => [ + %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}, + %{"type" => "Hashtag"} + ] + } + end + + test "returns object with emoji when object contains map tag" do + assert Transmogrifier.fix_emoji(%{ + "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}} + }) == %{ + "emoji" => %{"bib" => "/test"}, + "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"} + } + end + end + + describe "set_replies/1" do + clear_config([:activitypub, :note_replies_output_limit]) do + Pleroma.Config.put([:activitypub, :note_replies_output_limit], 2) + end + + test "returns unmodified object if activity doesn't have self-replies" do + data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json")) + assert Transmogrifier.set_replies(data) == data + end + + test "sets `replies` collection with a limited number of self-replies" do + [user, another_user] = insert_list(2, :user) + + {:ok, %{id: id1} = activity} = CommonAPI.post(user, %{"status" => "1"}) + + {:ok, %{id: id2} = self_reply1} = + CommonAPI.post(user, %{"status" => "self-reply 1", "in_reply_to_status_id" => id1}) + + {:ok, self_reply2} = + CommonAPI.post(user, %{"status" => "self-reply 2", "in_reply_to_status_id" => id1}) + + # Assuming to _not_ be present in `replies` due to :note_replies_output_limit is set to 2 + {:ok, _} = + CommonAPI.post(user, %{"status" => "self-reply 3", "in_reply_to_status_id" => id1}) + + {:ok, _} = + CommonAPI.post(user, %{ + "status" => "self-reply to self-reply", + "in_reply_to_status_id" => id2 + }) + + {:ok, _} = + CommonAPI.post(another_user, %{ + "status" => "another user's reply", + "in_reply_to_status_id" => id1 + }) + + object = Object.normalize(activity) + replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end) + + assert %{"type" => "Collection", "items" => ^replies_uris} = + Transmogrifier.set_replies(object.data)["replies"] + end + end end diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs index db6c68c44..e5ab54dd4 100644 --- a/test/web/activity_pub/utils_test.exs +++ b/test/web/activity_pub/utils_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.UtilsTest do @@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.AdminAPI.AccountView alias Pleroma.Web.CommonAPI import Pleroma.Factory @@ -87,6 +88,18 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do assert Utils.determine_explicit_mentions(object) == [] end + + test "works with an object has tags as map" do + object = %{ + "tag" => %{ + "type" => "Mention", + "href" => "https://example.com/~alyssa", + "name" => "Alyssa P. Hacker" + } + } + + assert Utils.determine_explicit_mentions(object) == ["https://example.com/~alyssa"] + end end describe "make_unlike_data/3" do @@ -285,7 +298,7 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do describe "update_follow_state_for_all/2" do test "updates the state of all Follow activities with the same actor and object" do - user = insert(:user, info: %{locked: true}) + user = insert(:user, locked: true) follower = insert(:user) {:ok, follow_activity} = ActivityPub.follow(follower, user) @@ -302,14 +315,14 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do {:ok, follow_activity_two} = Utils.update_follow_state_for_all(follow_activity_two, "accept") - assert Repo.get(Activity, follow_activity.id).data["state"] == "accept" - assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept" + assert refresh_record(follow_activity).data["state"] == "accept" + assert refresh_record(follow_activity_two).data["state"] == "accept" end end describe "update_follow_state/2" do test "updates the state of the given follow activity" do - user = insert(:user, info: %{locked: true}) + user = insert(:user, locked: true) follower = insert(:user) {:ok, follow_activity} = ActivityPub.follow(follower, user) @@ -325,8 +338,8 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do {:ok, follow_activity_two} = Utils.update_follow_state(follow_activity_two, "reject") - assert Repo.get(Activity, follow_activity.id).data["state"] == "pending" - assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject" + assert refresh_record(follow_activity).data["state"] == "pending" + assert refresh_record(follow_activity_two).data["state"] == "reject" end end @@ -403,4 +416,237 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do assert ^like_activity = Utils.get_existing_like(user.ap_id, object) end end + + describe "get_get_existing_announce/2" do + test "returns nil if announce not found" do + actor = insert(:user) + refute Utils.get_existing_announce(actor.ap_id, %{data: %{"id" => "test"}}) + end + + test "fetches existing announce" do + note_activity = insert(:note_activity) + assert object = Object.normalize(note_activity) + actor = insert(:user) + + {:ok, announce, _object} = ActivityPub.announce(actor, object) + assert Utils.get_existing_announce(actor.ap_id, object) == announce + end + end + + describe "fetch_latest_block/2" do + test "fetches last block activities" do + user1 = insert(:user) + user2 = insert(:user) + + assert {:ok, %Activity{} = _} = ActivityPub.block(user1, user2) + assert {:ok, %Activity{} = _} = ActivityPub.block(user1, user2) + assert {:ok, %Activity{} = activity} = ActivityPub.block(user1, user2) + + assert Utils.fetch_latest_block(user1, user2) == activity + end + end + + describe "recipient_in_message/3" do + test "returns true when recipient in `to`" do + recipient = insert(:user) + actor = insert(:user) + assert Utils.recipient_in_message(recipient, actor, %{"to" => recipient.ap_id}) + + assert Utils.recipient_in_message( + recipient, + actor, + %{"to" => [recipient.ap_id], "cc" => ""} + ) + end + + test "returns true when recipient in `cc`" do + recipient = insert(:user) + actor = insert(:user) + assert Utils.recipient_in_message(recipient, actor, %{"cc" => recipient.ap_id}) + + assert Utils.recipient_in_message( + recipient, + actor, + %{"cc" => [recipient.ap_id], "to" => ""} + ) + end + + test "returns true when recipient in `bto`" do + recipient = insert(:user) + actor = insert(:user) + assert Utils.recipient_in_message(recipient, actor, %{"bto" => recipient.ap_id}) + + assert Utils.recipient_in_message( + recipient, + actor, + %{"bcc" => "", "bto" => [recipient.ap_id]} + ) + end + + test "returns true when recipient in `bcc`" do + recipient = insert(:user) + actor = insert(:user) + assert Utils.recipient_in_message(recipient, actor, %{"bcc" => recipient.ap_id}) + + assert Utils.recipient_in_message( + recipient, + actor, + %{"bto" => "", "bcc" => [recipient.ap_id]} + ) + end + + test "returns true when message without addresses fields" do + recipient = insert(:user) + actor = insert(:user) + assert Utils.recipient_in_message(recipient, actor, %{"bccc" => recipient.ap_id}) + + assert Utils.recipient_in_message( + recipient, + actor, + %{"btod" => "", "bccc" => [recipient.ap_id]} + ) + end + + test "returns false" do + recipient = insert(:user) + actor = insert(:user) + refute Utils.recipient_in_message(recipient, actor, %{"to" => "ap_id"}) + end + end + + describe "lazy_put_activity_defaults/2" do + test "returns map with id and published data" do + note_activity = insert(:note_activity) + object = Object.normalize(note_activity) + res = Utils.lazy_put_activity_defaults(%{"context" => object.data["id"]}) + assert res["context"] == object.data["id"] + assert res["context_id"] == object.id + assert res["id"] + assert res["published"] + end + + test "returns map with fake id and published data" do + assert %{ + "context" => "pleroma:fakecontext", + "context_id" => -1, + "id" => "pleroma:fakeid", + "published" => _ + } = Utils.lazy_put_activity_defaults(%{}, true) + end + + test "returns activity data with object" do + note_activity = insert(:note_activity) + object = Object.normalize(note_activity) + + res = + Utils.lazy_put_activity_defaults(%{ + "context" => object.data["id"], + "object" => %{} + }) + + assert res["context"] == object.data["id"] + assert res["context_id"] == object.id + assert res["id"] + assert res["published"] + assert res["object"]["id"] + assert res["object"]["published"] + assert res["object"]["context"] == object.data["id"] + assert res["object"]["context_id"] == object.id + end + end + + describe "make_flag_data" do + test "returns empty map when params is invalid" do + assert Utils.make_flag_data(%{}, %{}) == %{} + end + + test "returns map with Flag object" do + reporter = insert(:user) + target_account = insert(:user) + {:ok, activity} = CommonAPI.post(target_account, %{"status" => "foobar"}) + context = Utils.generate_context_id() + content = "foobar" + + target_ap_id = target_account.ap_id + activity_ap_id = activity.data["id"] + + res = + Utils.make_flag_data( + %{ + actor: reporter, + context: context, + account: target_account, + statuses: [%{"id" => activity.data["id"]}], + content: content + }, + %{} + ) + + note_obj = %{ + "type" => "Note", + "id" => activity_ap_id, + "content" => content, + "published" => activity.object.data["published"], + "actor" => AccountView.render("show.json", %{user: target_account}) + } + + assert %{ + "type" => "Flag", + "content" => ^content, + "context" => ^context, + "object" => [^target_ap_id, ^note_obj], + "state" => "open" + } = res + end + end + + describe "add_announce_to_object/2" do + test "adds actor to announcement" do + user = insert(:user) + object = insert(:note) + + activity = + insert(:note_activity, + data: %{ + "actor" => user.ap_id, + "cc" => [Pleroma.Constants.as_public()] + } + ) + + assert {:ok, updated_object} = Utils.add_announce_to_object(activity, object) + assert updated_object.data["announcements"] == [user.ap_id] + assert updated_object.data["announcement_count"] == 1 + end + end + + describe "remove_announce_from_object/2" do + test "removes actor from announcements" do + user = insert(:user) + user2 = insert(:user) + + object = + insert(:note, + data: %{"announcements" => [user.ap_id, user2.ap_id], "announcement_count" => 2} + ) + + activity = insert(:note_activity, data: %{"actor" => user.ap_id}) + + assert {:ok, updated_object} = Utils.remove_announce_from_object(activity, object) + assert updated_object.data["announcements"] == [user2.ap_id] + assert updated_object.data["announcement_count"] == 1 + end + end + + describe "get_cached_emoji_reactions/1" do + test "returns the data or an emtpy list" do + object = insert(:note) + assert Utils.get_cached_emoji_reactions(object) == [] + + object = insert(:note, data: %{"reactions" => [["x", ["lain"]]]}) + assert Utils.get_cached_emoji_reactions(object) == [["x", ["lain"]]] + + object = insert(:note, data: %{"reactions" => %{}}) + assert Utils.get_cached_emoji_reactions(object) == [] + end + end end diff --git a/test/web/activity_pub/views/object_view_test.exs b/test/web/activity_pub/views/object_view_test.exs index 13447dc29..09866e99b 100644 --- a/test/web/activity_pub/views/object_view_test.exs +++ b/test/web/activity_pub/views/object_view_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.ObjectViewTest do @@ -36,6 +36,26 @@ defmodule Pleroma.Web.ActivityPub.ObjectViewTest do assert result["@context"] end + describe "note activity's `replies` collection rendering" do + clear_config([:activitypub, :note_replies_output_limit]) do + Pleroma.Config.put([:activitypub, :note_replies_output_limit], 5) + end + + test "renders `replies` collection for a note activity" do + user = insert(:user) + activity = insert(:note_activity, user: user) + + {:ok, self_reply1} = + CommonAPI.post(user, %{"status" => "self-reply 1", "in_reply_to_status_id" => activity.id}) + + replies_uris = [self_reply1.object.data["id"]] + result = ObjectView.render("object.json", %{object: refresh_record(activity)}) + + assert %{"type" => "Collection", "items" => ^replies_uris} = + get_in(result, ["object", "replies"]) + end + end + test "renders a like activity" do note = insert(:note_activity) object = Object.normalize(note) diff --git a/test/web/activity_pub/views/user_view_test.exs b/test/web/activity_pub/views/user_view_test.exs index dbb44a8a2..ecb2dc386 100644 --- a/test/web/activity_pub/views/user_view_test.exs +++ b/test/web/activity_pub/views/user_view_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.UserViewTest do @@ -29,7 +29,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do {:ok, user} = insert(:user) - |> User.upgrade_changeset(%{info: %{fields: fields}}) + |> User.upgrade_changeset(%{fields: fields}) |> User.update_and_set_cache() assert %{ @@ -37,6 +37,22 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do } = UserView.render("user.json", %{user: user}) end + test "Renders with emoji tags" do + user = insert(:user, emoji: [%{"bib" => "/test"}]) + + assert %{ + "tag" => [ + %{ + "icon" => %{"type" => "Image", "url" => "/test"}, + "id" => "/test", + "name" => ":bib:", + "type" => "Emoji", + "updated" => "1970-01-01T00:00:00Z" + } + ] + } = UserView.render("user.json", %{user: user}) + end + test "Does not add an avatar image if the user hasn't set one" do user = insert(:user) {:ok, user} = User.ensure_keys_present(user) @@ -48,9 +64,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do user = insert(:user, avatar: %{"url" => [%{"href" => "https://someurl"}]}, - info: %{ - banner: %{"url" => [%{"href" => "https://somebanner"}]} - } + banner: %{"url" => [%{"href" => "https://somebanner"}]} ) {:ok, user} = User.ensure_keys_present(user) @@ -60,6 +74,12 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do assert result["image"]["url"] == "https://somebanner" end + test "renders an invisible user with the invisible property set to true" do + user = insert(:user, invisible: true) + + assert %{"invisible" => true} = UserView.render("service.json", %{user: user}) + end + describe "endpoints" do test "local users have a usable endpoints structure" do user = insert(:user) @@ -105,9 +125,17 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do other_user = insert(:user) {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user}) - info = Map.put(user.info, :hide_followers, true) - user = Map.put(user, :info, info) - assert %{"totalItems" => 0} = UserView.render("followers.json", %{user: user}) + user = Map.merge(user, %{hide_followers_count: true, hide_followers: true}) + refute UserView.render("followers.json", %{user: user}) |> Map.has_key?("totalItems") + end + + test "sets correct totalItems when followers are hidden but the follower counter is not" do + user = insert(:user) + other_user = insert(:user) + {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) + assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user}) + user = Map.merge(user, %{hide_followers_count: false, hide_followers: true}) + assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user}) end end @@ -117,40 +145,48 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do other_user = insert(:user) {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user) assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user}) - info = Map.put(user.info, :hide_follows, true) - user = Map.put(user, :info, info) + user = Map.merge(user, %{hide_follows_count: true, hide_follows: true}) assert %{"totalItems" => 0} = UserView.render("following.json", %{user: user}) end - test "activity collection page aginates correctly" do + test "sets correct totalItems when follows are hidden but the follow counter is not" do user = insert(:user) + other_user = insert(:user) + {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user) + assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user}) + user = Map.merge(user, %{hide_follows_count: false, hide_follows: true}) + assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user}) + end + end - posts = - for i <- 0..25 do - {:ok, activity} = CommonAPI.post(user, %{"status" => "post #{i}"}) - activity - end + test "activity collection page aginates correctly" do + user = insert(:user) - # outbox sorts chronologically, newest first, with ten per page - posts = Enum.reverse(posts) + posts = + for i <- 0..25 do + {:ok, activity} = CommonAPI.post(user, %{"status" => "post #{i}"}) + activity + end - %{"next" => next_url} = - UserView.render("activity_collection_page.json", %{ - iri: "#{user.ap_id}/outbox", - activities: Enum.take(posts, 10) - }) + # outbox sorts chronologically, newest first, with ten per page + posts = Enum.reverse(posts) - next_id = Enum.at(posts, 9).id - assert next_url =~ next_id + %{"next" => next_url} = + UserView.render("activity_collection_page.json", %{ + iri: "#{user.ap_id}/outbox", + activities: Enum.take(posts, 10) + }) - %{"next" => next_url} = - UserView.render("activity_collection_page.json", %{ - iri: "#{user.ap_id}/outbox", - activities: Enum.take(Enum.drop(posts, 10), 10) - }) + next_id = Enum.at(posts, 9).id + assert next_url =~ next_id - next_id = Enum.at(posts, 19).id - assert next_url =~ next_id - end + %{"next" => next_url} = + UserView.render("activity_collection_page.json", %{ + iri: "#{user.ap_id}/outbox", + activities: Enum.take(Enum.drop(posts, 10), 10) + }) + + next_id = Enum.at(posts, 19).id + assert next_url =~ next_id end end diff --git a/test/web/activity_pub/visibilty_test.exs b/test/web/activity_pub/visibilty_test.exs index b62a89e68..5b91630d4 100644 --- a/test/web/activity_pub/visibilty_test.exs +++ b/test/web/activity_pub/visibilty_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.VisibilityTest do @@ -212,7 +212,8 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do test "returns true if user following to author" do author = insert(:user) - user = insert(:user, following: [author.ap_id]) + user = insert(:user) + Pleroma.User.follow(user, author) activity = insert(:note_activity, diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index 3b6d75a4c..8009d4386 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -1,48 +1,194 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do use Pleroma.Web.ConnCase + use Oban.Testing, repo: Pleroma.Repo + + import Pleroma.Factory + import ExUnit.CaptureLog alias Pleroma.Activity + alias Pleroma.Config + alias Pleroma.ConfigDB alias Pleroma.HTML alias Pleroma.ModerationLog alias Pleroma.Repo + alias Pleroma.ReportNote + alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.UserInviteToken + alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MediaProxy - import Pleroma.Factory - describe "/api/pleroma/admin/users" do - test "Delete" do - admin = insert(:user, info: %{is_admin: true}) + setup_all do + Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) + + :ok + end + + setup do + admin = insert(:user, is_admin: true) + token = insert(:oauth_admin_token, user: admin) + + conn = + build_conn() + |> assign(:user, admin) + |> assign(:token, token) + + {:ok, %{admin: admin, token: token, conn: conn}} + end + + describe "with [:auth, :enforce_oauth_admin_scope_usage]," do + clear_config([:auth, :enforce_oauth_admin_scope_usage]) do + Config.put([:auth, :enforce_oauth_admin_scope_usage], true) + end + + test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or broader scope", + %{admin: admin} do + user = insert(:user) + url = "/api/pleroma/admin/users/#{user.nickname}" + + good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"]) + good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"]) + good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"]) + + bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts"]) + bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"]) + bad_token3 = nil + + for good_token <- [good_token1, good_token2, good_token3] do + conn = + build_conn() + |> assign(:user, admin) + |> assign(:token, good_token) + |> get(url) + + assert json_response(conn, 200) + end + + for good_token <- [good_token1, good_token2, good_token3] do + conn = + build_conn() + |> assign(:user, nil) + |> assign(:token, good_token) + |> get(url) + + assert json_response(conn, :forbidden) + end + + for bad_token <- [bad_token1, bad_token2, bad_token3] do + conn = + build_conn() + |> assign(:user, admin) + |> assign(:token, bad_token) + |> get(url) + + assert json_response(conn, :forbidden) + end + end + end + + describe "unless [:auth, :enforce_oauth_admin_scope_usage]," do + clear_config([:auth, :enforce_oauth_admin_scope_usage]) do + Config.put([:auth, :enforce_oauth_admin_scope_usage], false) + end + + test "GET /api/pleroma/admin/users/:nickname requires " <> + "read:accounts or admin:read:accounts or broader scope", + %{admin: admin} do + user = insert(:user) + url = "/api/pleroma/admin/users/#{user.nickname}" + + good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"]) + good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"]) + good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"]) + good_token4 = insert(:oauth_token, user: admin, scopes: ["read:accounts"]) + good_token5 = insert(:oauth_token, user: admin, scopes: ["read"]) + + good_tokens = [good_token1, good_token2, good_token3, good_token4, good_token5] + + bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts:partial"]) + bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"]) + bad_token3 = nil + + for good_token <- good_tokens do + conn = + build_conn() + |> assign(:user, admin) + |> assign(:token, good_token) + |> get(url) + + assert json_response(conn, 200) + end + + for good_token <- good_tokens do + conn = + build_conn() + |> assign(:user, nil) + |> assign(:token, good_token) + |> get(url) + + assert json_response(conn, :forbidden) + end + + for bad_token <- [bad_token1, bad_token2, bad_token3] do + conn = + build_conn() + |> assign(:user, admin) + |> assign(:token, bad_token) + |> get(url) + + assert json_response(conn, :forbidden) + end + end + end + + describe "DELETE /api/pleroma/admin/users" do + test "single user", %{admin: admin, conn: conn} do user = insert(:user) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> delete("/api/pleroma/admin/users?nickname=#{user.nickname}") log_entry = Repo.one(ModerationLog) - assert log_entry.data["subject"]["nickname"] == user.nickname - assert log_entry.data["action"] == "delete" - assert ModerationLog.get_log_entry_message(log_entry) == - "@#{admin.nickname} deleted user @#{user.nickname}" + "@#{admin.nickname} deleted users: @#{user.nickname}" assert json_response(conn, 200) == user.nickname end - test "Create" do - admin = insert(:user, info: %{is_admin: true}) + test "multiple users", %{admin: admin, conn: conn} do + user_one = insert(:user) + user_two = insert(:user) conn = - build_conn() - |> assign(:user, admin) + conn + |> put_req_header("accept", "application/json") + |> delete("/api/pleroma/admin/users", %{ + nicknames: [user_one.nickname, user_two.nickname] + }) + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} deleted users: @#{user_one.nickname}, @#{user_two.nickname}" + + response = json_response(conn, 200) + assert response -- [user_one.nickname, user_two.nickname] == [] + end + end + + describe "/api/pleroma/admin/users" do + test "Create", %{conn: conn} do + conn = + conn |> put_req_header("accept", "application/json") |> post("/api/pleroma/admin/users", %{ "users" => [ @@ -67,13 +213,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do assert ["lain", "lain2"] -- Enum.map(log_entry.data["subjects"], & &1["nickname"]) == [] end - test "Cannot create user with exisiting email" do - admin = insert(:user, info: %{is_admin: true}) + test "Cannot create user with existing email", %{conn: conn} do user = insert(:user) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> post("/api/pleroma/admin/users", %{ "users" => [ @@ -98,13 +242,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do ] end - test "Cannot create user with exisiting nickname" do - admin = insert(:user, info: %{is_admin: true}) + test "Cannot create user with existing nickname", %{conn: conn} do user = insert(:user) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> post("/api/pleroma/admin/users", %{ "users" => [ @@ -129,13 +271,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do ] end - test "Multiple user creation works in transaction" do - admin = insert(:user, info: %{is_admin: true}) + test "Multiple user creation works in transaction", %{conn: conn} do user = insert(:user) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> post("/api/pleroma/admin/users", %{ "users" => [ @@ -179,13 +319,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do describe "/api/pleroma/admin/users/:nickname" do test "Show", %{conn: conn} do - admin = insert(:user, info: %{is_admin: true}) user = insert(:user) - conn = - conn - |> assign(:user, admin) - |> get("/api/pleroma/admin/users/#{user.nickname}") + conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}") expected = %{ "deactivated" => false, @@ -195,33 +331,28 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "roles" => %{"admin" => false, "moderator" => false}, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } assert expected == json_response(conn, 200) end test "when the user doesn't exist", %{conn: conn} do - admin = insert(:user, info: %{is_admin: true}) user = build(:user) - conn = - conn - |> assign(:user, admin) - |> get("/api/pleroma/admin/users/#{user.nickname}") + conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}") assert "Not found" == json_response(conn, 404) end end describe "/api/pleroma/admin/users/follow" do - test "allows to force-follow another user" do - admin = insert(:user, info: %{is_admin: true}) + test "allows to force-follow another user", %{admin: admin, conn: conn} do user = insert(:user) follower = insert(:user) - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> post("/api/pleroma/admin/users/follow", %{ "follower" => follower.nickname, @@ -241,15 +372,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do end describe "/api/pleroma/admin/users/unfollow" do - test "allows to force-unfollow another user" do - admin = insert(:user, info: %{is_admin: true}) + test "allows to force-unfollow another user", %{admin: admin, conn: conn} do user = insert(:user) follower = insert(:user) User.follow(follower, user) - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> post("/api/pleroma/admin/users/unfollow", %{ "follower" => follower.nickname, @@ -269,23 +398,20 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do end describe "PUT /api/pleroma/admin/users/tag" do - setup do - admin = insert(:user, info: %{is_admin: true}) + setup %{conn: conn} do user1 = insert(:user, %{tags: ["x"]}) user2 = insert(:user, %{tags: ["y"]}) user3 = insert(:user, %{tags: ["unchanged"]}) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> put( - "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=#{ - user2.nickname - }&tags[]=foo&tags[]=bar" + "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <> + "#{user2.nickname}&tags[]=foo&tags[]=bar" ) - %{conn: conn, admin: admin, user1: user1, user2: user2, user3: user3} + %{conn: conn, user1: user1, user2: user2, user3: user3} end test "it appends specified tags to users with specified nicknames", %{ @@ -318,23 +444,20 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do end describe "DELETE /api/pleroma/admin/users/tag" do - setup do - admin = insert(:user, info: %{is_admin: true}) + setup %{conn: conn} do user1 = insert(:user, %{tags: ["x"]}) user2 = insert(:user, %{tags: ["y", "z"]}) user3 = insert(:user, %{tags: ["unchanged"]}) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> delete( - "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=#{ - user2.nickname - }&tags[]=x&tags[]=z" + "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <> + "#{user2.nickname}&tags[]=x&tags[]=z" ) - %{conn: conn, admin: admin, user1: user1, user2: user2, user3: user3} + %{conn: conn, user1: user1, user2: user2, user3: user3} end test "it removes specified tags from users with specified nicknames", %{ @@ -367,12 +490,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do end describe "/api/pleroma/admin/users/:nickname/permission_group" do - test "GET is giving user_info" do - admin = insert(:user, info: %{is_admin: true}) - + test "GET is giving user_info", %{admin: admin, conn: conn} do conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> get("/api/pleroma/admin/users/#{admin.nickname}/permission_group/") @@ -382,13 +502,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do } end - test "/:right POST, can add to a permission group" do - admin = insert(:user, info: %{is_admin: true}) + test "/:right POST, can add to a permission group", %{admin: admin, conn: conn} do user = insert(:user) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> post("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin") @@ -402,121 +520,97 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "@#{admin.nickname} made @#{user.nickname} admin" end - test "/:right DELETE, can remove from a permission group" do - admin = insert(:user, info: %{is_admin: true}) - user = insert(:user, info: %{is_admin: true}) + test "/:right POST, can add to a permission group (multiple)", %{admin: admin, conn: conn} do + user_one = insert(:user) + user_two = insert(:user) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") - |> delete("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin") + |> post("/api/pleroma/admin/users/permission_group/admin", %{ + nicknames: [user_one.nickname, user_two.nickname] + }) - assert json_response(conn, 200) == %{ - "is_admin" => false - } + assert json_response(conn, 200) == %{"is_admin" => true} log_entry = Repo.one(ModerationLog) assert ModerationLog.get_log_entry_message(log_entry) == - "@#{admin.nickname} revoked admin role from @#{user.nickname}" + "@#{admin.nickname} made @#{user_one.nickname}, @#{user_two.nickname} admin" end - end - describe "PUT /api/pleroma/admin/users/:nickname/activation_status" do - setup %{conn: conn} do - admin = insert(:user, info: %{is_admin: true}) + test "/:right DELETE, can remove from a permission group", %{admin: admin, conn: conn} do + user = insert(:user, is_admin: true) conn = conn - |> assign(:user, admin) |> put_req_header("accept", "application/json") + |> delete("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin") - %{conn: conn, admin: admin} - end - - test "deactivates the user", %{conn: conn, admin: admin} do - user = insert(:user) - - conn = - conn - |> put("/api/pleroma/admin/users/#{user.nickname}/activation_status", %{status: false}) - - user = User.get_cached_by_id(user.id) - assert user.info.deactivated == true - assert json_response(conn, :no_content) + assert json_response(conn, 200) == %{"is_admin" => false} log_entry = Repo.one(ModerationLog) assert ModerationLog.get_log_entry_message(log_entry) == - "@#{admin.nickname} deactivated user @#{user.nickname}" + "@#{admin.nickname} revoked admin role from @#{user.nickname}" end - test "activates the user", %{conn: conn, admin: admin} do - user = insert(:user, info: %{deactivated: true}) + test "/:right DELETE, can remove from a permission group (multiple)", %{ + admin: admin, + conn: conn + } do + user_one = insert(:user, is_admin: true) + user_two = insert(:user, is_admin: true) conn = conn - |> put("/api/pleroma/admin/users/#{user.nickname}/activation_status", %{status: true}) + |> put_req_header("accept", "application/json") + |> delete("/api/pleroma/admin/users/permission_group/admin", %{ + nicknames: [user_one.nickname, user_two.nickname] + }) - user = User.get_cached_by_id(user.id) - assert user.info.deactivated == false - assert json_response(conn, :no_content) + assert json_response(conn, 200) == %{"is_admin" => false} log_entry = Repo.one(ModerationLog) assert ModerationLog.get_log_entry_message(log_entry) == - "@#{admin.nickname} activated user @#{user.nickname}" - end - - test "returns 403 when requested by a non-admin", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> put("/api/pleroma/admin/users/#{user.nickname}/activation_status", %{status: false}) - - assert json_response(conn, :forbidden) + "@#{admin.nickname} revoked admin role from @#{user_one.nickname}, @#{ + user_two.nickname + }" end end describe "POST /api/pleroma/admin/email_invite, with valid config" do - setup do - [user: insert(:user, info: %{is_admin: true})] - end - clear_config([:instance, :registrations_open]) do - Pleroma.Config.put([:instance, :registrations_open], false) + Config.put([:instance, :registrations_open], false) end clear_config([:instance, :invites_enabled]) do - Pleroma.Config.put([:instance, :invites_enabled], true) + Config.put([:instance, :invites_enabled], true) end - test "sends invitation and returns 204", %{conn: conn, user: user} do + test "sends invitation and returns 204", %{admin: admin, conn: conn} do recipient_email = "foo@bar.com" recipient_name = "J. D." conn = - conn - |> assign(:user, user) - |> post( + post( + conn, "/api/pleroma/admin/users/email_invite?email=#{recipient_email}&name=#{recipient_name}" ) assert json_response(conn, :no_content) - token_record = List.last(Pleroma.Repo.all(Pleroma.UserInviteToken)) + token_record = List.last(Repo.all(Pleroma.UserInviteToken)) assert token_record refute token_record.used - notify_email = Pleroma.Config.get([:instance, :notify_email]) - instance_name = Pleroma.Config.get([:instance, :name]) + notify_email = Config.get([:instance, :notify_email]) + instance_name = Config.get([:instance, :name]) email = Pleroma.Emails.UserEmail.user_invitation_email( - user, + admin, token_record, recipient_email, recipient_name @@ -529,12 +623,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do ) end - test "it returns 403 if requested by a non-admin", %{conn: conn} do + test "it returns 403 if requested by a non-admin" do non_admin_user = insert(:user) + token = insert(:oauth_token, user: non_admin_user) conn = - conn + build_conn() |> assign(:user, non_admin_user) + |> assign(:token, token) |> post("/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") assert json_response(conn, :forbidden) @@ -542,74 +638,42 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do end describe "POST /api/pleroma/admin/users/email_invite, with invalid config" do - setup do - [user: insert(:user, info: %{is_admin: true})] - end - clear_config([:instance, :registrations_open]) clear_config([:instance, :invites_enabled]) - test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn, user: user} do - Pleroma.Config.put([:instance, :registrations_open], false) - Pleroma.Config.put([:instance, :invites_enabled], false) + test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn} do + Config.put([:instance, :registrations_open], false) + Config.put([:instance, :invites_enabled], false) - conn = - conn - |> assign(:user, user) - |> post("/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") + conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") assert json_response(conn, :internal_server_error) end - test "it returns 500 if `registrations_open` is enabled", %{conn: conn, user: user} do - Pleroma.Config.put([:instance, :registrations_open], true) - Pleroma.Config.put([:instance, :invites_enabled], true) + test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do + Config.put([:instance, :registrations_open], true) + Config.put([:instance, :invites_enabled], true) - conn = - conn - |> assign(:user, user) - |> post("/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") + conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") assert json_response(conn, :internal_server_error) end end - test "/api/pleroma/admin/users/invite_token" do - admin = insert(:user, info: %{is_admin: true}) - - conn = - build_conn() - |> assign(:user, admin) - |> put_req_header("accept", "application/json") - |> get("/api/pleroma/admin/users/invite_token") - - assert conn.status == 200 - end - - test "/api/pleroma/admin/users/:nickname/password_reset" do - admin = insert(:user, info: %{is_admin: true}) + test "/api/pleroma/admin/users/:nickname/password_reset", %{conn: conn} do user = insert(:user) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> get("/api/pleroma/admin/users/#{user.nickname}/password_reset") - assert conn.status == 200 + resp = json_response(conn, 200) + + assert Regex.match?(~r/(http:\/\/|https:\/\/)/, resp["link"]) end describe "GET /api/pleroma/admin/users" do - setup do - admin = insert(:user, info: %{is_admin: true}) - - conn = - build_conn() - |> assign(:user, admin) - - {:ok, conn: conn, admin: admin} - end - test "renders users array for the first page", %{conn: conn, admin: admin} do user = insert(:user, local: false, tags: ["foo", "bar"]) conn = get(conn, "/api/pleroma/admin/users?page=1") @@ -617,24 +681,26 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do users = [ %{ - "deactivated" => admin.info.deactivated, + "deactivated" => admin.deactivated, "id" => admin.id, "nickname" => admin.nickname, "roles" => %{"admin" => true, "moderator" => false}, "local" => true, "tags" => [], "avatar" => User.avatar_url(admin) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(admin.name || admin.nickname) + "display_name" => HTML.strip_tags(admin.name || admin.nickname), + "confirmation_pending" => false }, %{ - "deactivated" => user.info.deactivated, + "deactivated" => user.deactivated, "id" => user.id, "nickname" => user.nickname, "roles" => %{"admin" => false, "moderator" => false}, "local" => false, "tags" => ["foo", "bar"], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } ] |> Enum.sort_by(& &1["nickname"]) @@ -668,14 +734,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "page_size" => 50, "users" => [ %{ - "deactivated" => user.info.deactivated, + "deactivated" => user.deactivated, "id" => user.id, "nickname" => user.nickname, "roles" => %{"admin" => false, "moderator" => false}, "local" => true, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } ] } @@ -692,14 +759,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "page_size" => 50, "users" => [ %{ - "deactivated" => user.info.deactivated, + "deactivated" => user.deactivated, "id" => user.id, "nickname" => user.nickname, "roles" => %{"admin" => false, "moderator" => false}, "local" => true, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } ] } @@ -716,14 +784,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "page_size" => 50, "users" => [ %{ - "deactivated" => user.info.deactivated, + "deactivated" => user.deactivated, "id" => user.id, "nickname" => user.nickname, "roles" => %{"admin" => false, "moderator" => false}, "local" => true, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } ] } @@ -740,14 +809,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "page_size" => 50, "users" => [ %{ - "deactivated" => user.info.deactivated, + "deactivated" => user.deactivated, "id" => user.id, "nickname" => user.nickname, "roles" => %{"admin" => false, "moderator" => false}, "local" => true, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } ] } @@ -764,14 +834,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "page_size" => 50, "users" => [ %{ - "deactivated" => user.info.deactivated, + "deactivated" => user.deactivated, "id" => user.id, "nickname" => user.nickname, "roles" => %{"admin" => false, "moderator" => false}, "local" => true, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } ] } @@ -788,14 +859,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "page_size" => 1, "users" => [ %{ - "deactivated" => user.info.deactivated, + "deactivated" => user.deactivated, "id" => user.id, "nickname" => user.nickname, "roles" => %{"admin" => false, "moderator" => false}, "local" => true, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } ] } @@ -807,21 +879,23 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "page_size" => 1, "users" => [ %{ - "deactivated" => user2.info.deactivated, + "deactivated" => user2.deactivated, "id" => user2.id, "nickname" => user2.nickname, "roles" => %{"admin" => false, "moderator" => false}, "local" => true, "tags" => [], "avatar" => User.avatar_url(user2) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user2.name || user2.nickname) + "display_name" => HTML.strip_tags(user2.name || user2.nickname), + "confirmation_pending" => false } ] } end test "only local users" do - admin = insert(:user, info: %{is_admin: true}, nickname: "john") + admin = insert(:user, is_admin: true, nickname: "john") + token = insert(:oauth_admin_token, user: admin) user = insert(:user, nickname: "bob") insert(:user, nickname: "bobb", local: false) @@ -829,6 +903,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do conn = build_conn() |> assign(:user, admin) + |> assign(:token, token) |> get("/api/pleroma/admin/users?query=bo&filters=local") assert json_response(conn, 200) == %{ @@ -836,51 +911,51 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "page_size" => 50, "users" => [ %{ - "deactivated" => user.info.deactivated, + "deactivated" => user.deactivated, "id" => user.id, "nickname" => user.nickname, "roles" => %{"admin" => false, "moderator" => false}, "local" => true, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } ] } end - test "only local users with no query", %{admin: old_admin} do - admin = insert(:user, info: %{is_admin: true}, nickname: "john") + test "only local users with no query", %{conn: conn, admin: old_admin} do + admin = insert(:user, is_admin: true, nickname: "john") user = insert(:user, nickname: "bob") insert(:user, nickname: "bobb", local: false) - conn = - build_conn() - |> assign(:user, admin) - |> get("/api/pleroma/admin/users?filters=local") + conn = get(conn, "/api/pleroma/admin/users?filters=local") users = [ %{ - "deactivated" => user.info.deactivated, + "deactivated" => user.deactivated, "id" => user.id, "nickname" => user.nickname, "roles" => %{"admin" => false, "moderator" => false}, "local" => true, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false }, %{ - "deactivated" => admin.info.deactivated, + "deactivated" => admin.deactivated, "id" => admin.id, "nickname" => admin.nickname, "roles" => %{"admin" => true, "moderator" => false}, "local" => true, "tags" => [], "avatar" => User.avatar_url(admin) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(admin.name || admin.nickname) + "display_name" => HTML.strip_tags(admin.name || admin.nickname), + "confirmation_pending" => false }, %{ "deactivated" => false, @@ -890,7 +965,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "roles" => %{"admin" => true, "moderator" => false}, "tags" => [], "avatar" => User.avatar_url(old_admin) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname) + "display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname), + "confirmation_pending" => false } ] |> Enum.sort_by(& &1["nickname"]) @@ -903,7 +979,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do end test "load only admins", %{conn: conn, admin: admin} do - second_admin = insert(:user, info: %{is_admin: true}) + second_admin = insert(:user, is_admin: true) insert(:user) insert(:user) @@ -919,7 +995,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "local" => admin.local, "tags" => [], "avatar" => User.avatar_url(admin) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(admin.name || admin.nickname) + "display_name" => HTML.strip_tags(admin.name || admin.nickname), + "confirmation_pending" => false }, %{ "deactivated" => false, @@ -929,7 +1006,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "local" => second_admin.local, "tags" => [], "avatar" => User.avatar_url(second_admin) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname) + "display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname), + "confirmation_pending" => false } ] |> Enum.sort_by(& &1["nickname"]) @@ -942,7 +1020,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do end test "load only moderators", %{conn: conn} do - moderator = insert(:user, info: %{is_moderator: true}) + moderator = insert(:user, is_moderator: true) insert(:user) insert(:user) @@ -960,7 +1038,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "local" => moderator.local, "tags" => [], "avatar" => User.avatar_url(moderator) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(moderator.name || moderator.nickname) + "display_name" => HTML.strip_tags(moderator.name || moderator.nickname), + "confirmation_pending" => false } ] } @@ -984,7 +1063,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "local" => user1.local, "tags" => ["first"], "avatar" => User.avatar_url(user1) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user1.name || user1.nickname) + "display_name" => HTML.strip_tags(user1.name || user1.nickname), + "confirmation_pending" => false }, %{ "deactivated" => false, @@ -994,7 +1074,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "local" => user2.local, "tags" => ["second"], "avatar" => User.avatar_url(user2) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user2.name || user2.nickname) + "display_name" => HTML.strip_tags(user2.name || user2.nickname), + "confirmation_pending" => false } ] |> Enum.sort_by(& &1["nickname"]) @@ -1007,15 +1088,17 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do end test "it works with multiple filters" do - admin = insert(:user, nickname: "john", info: %{is_admin: true}) - user = insert(:user, nickname: "bob", local: false, info: %{deactivated: true}) + admin = insert(:user, nickname: "john", is_admin: true) + token = insert(:oauth_admin_token, user: admin) + user = insert(:user, nickname: "bob", local: false, deactivated: true) - insert(:user, nickname: "ken", local: true, info: %{deactivated: true}) - insert(:user, nickname: "bobb", local: false, info: %{deactivated: false}) + insert(:user, nickname: "ken", local: true, deactivated: true) + insert(:user, nickname: "bobb", local: false, deactivated: false) conn = build_conn() |> assign(:user, admin) + |> assign(:token, token) |> get("/api/pleroma/admin/users?filters=deactivated,external") assert json_response(conn, 200) == %{ @@ -1023,63 +1106,115 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "page_size" => 50, "users" => [ %{ - "deactivated" => user.info.deactivated, + "deactivated" => user.deactivated, "id" => user.id, "nickname" => user.nickname, "roles" => %{"admin" => false, "moderator" => false}, "local" => user.local, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false + } + ] + } + end + + test "it omits relay user", %{admin: admin, conn: conn} do + assert %User{} = Relay.get_actor() + + conn = get(conn, "/api/pleroma/admin/users") + + assert json_response(conn, 200) == %{ + "count" => 1, + "page_size" => 50, + "users" => [ + %{ + "deactivated" => admin.deactivated, + "id" => admin.id, + "nickname" => admin.nickname, + "roles" => %{"admin" => true, "moderator" => false}, + "local" => true, + "tags" => [], + "avatar" => User.avatar_url(admin) |> MediaProxy.url(), + "display_name" => HTML.strip_tags(admin.name || admin.nickname), + "confirmation_pending" => false } ] } end end - test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation" do - admin = insert(:user, info: %{is_admin: true}) - user = insert(:user) + test "PATCH /api/pleroma/admin/users/activate", %{admin: admin, conn: conn} do + user_one = insert(:user, deactivated: true) + user_two = insert(:user, deactivated: true) conn = - build_conn() - |> assign(:user, admin) - |> patch("/api/pleroma/admin/users/#{user.nickname}/toggle_activation") + patch( + conn, + "/api/pleroma/admin/users/activate", + %{nicknames: [user_one.nickname, user_two.nickname]} + ) + + response = json_response(conn, 200) + assert Enum.map(response["users"], & &1["deactivated"]) == [false, false] + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} activated users: @#{user_one.nickname}, @#{user_two.nickname}" + end + + test "PATCH /api/pleroma/admin/users/deactivate", %{admin: admin, conn: conn} do + user_one = insert(:user, deactivated: false) + user_two = insert(:user, deactivated: false) + + conn = + patch( + conn, + "/api/pleroma/admin/users/deactivate", + %{nicknames: [user_one.nickname, user_two.nickname]} + ) + + response = json_response(conn, 200) + assert Enum.map(response["users"], & &1["deactivated"]) == [true, true] + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} deactivated users: @#{user_one.nickname}, @#{user_two.nickname}" + end + + test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do + user = insert(:user) + + conn = patch(conn, "/api/pleroma/admin/users/#{user.nickname}/toggle_activation") assert json_response(conn, 200) == %{ - "deactivated" => !user.info.deactivated, + "deactivated" => !user.deactivated, "id" => user.id, "nickname" => user.nickname, "roles" => %{"admin" => false, "moderator" => false}, "local" => true, "tags" => [], "avatar" => User.avatar_url(user) |> MediaProxy.url(), - "display_name" => HTML.strip_tags(user.name || user.nickname) + "display_name" => HTML.strip_tags(user.name || user.nickname), + "confirmation_pending" => false } log_entry = Repo.one(ModerationLog) assert ModerationLog.get_log_entry_message(log_entry) == - "@#{admin.nickname} deactivated user @#{user.nickname}" + "@#{admin.nickname} deactivated users: @#{user.nickname}" end - describe "GET /api/pleroma/admin/users/invite_token" do - setup do - admin = insert(:user, info: %{is_admin: true}) - - conn = - build_conn() - |> assign(:user, admin) - - {:ok, conn: conn} - end - + describe "POST /api/pleroma/admin/users/invite_token" do test "without options", %{conn: conn} do - conn = get(conn, "/api/pleroma/admin/users/invite_token") + conn = post(conn, "/api/pleroma/admin/users/invite_token") - token = json_response(conn, 200) - invite = UserInviteToken.find_by_token!(token) + invite_json = json_response(conn, 200) + invite = UserInviteToken.find_by_token!(invite_json["token"]) refute invite.used refute invite.expires_at refute invite.max_use @@ -1088,12 +1223,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do test "with expires_at", %{conn: conn} do conn = - get(conn, "/api/pleroma/admin/users/invite_token", %{ - "invite" => %{"expires_at" => Date.to_string(Date.utc_today())} + post(conn, "/api/pleroma/admin/users/invite_token", %{ + "expires_at" => Date.to_string(Date.utc_today()) }) - token = json_response(conn, 200) - invite = UserInviteToken.find_by_token!(token) + invite_json = json_response(conn, 200) + invite = UserInviteToken.find_by_token!(invite_json["token"]) refute invite.used assert invite.expires_at == Date.utc_today() @@ -1102,13 +1237,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do end test "with max_use", %{conn: conn} do - conn = - get(conn, "/api/pleroma/admin/users/invite_token", %{ - "invite" => %{"max_use" => 150} - }) + conn = post(conn, "/api/pleroma/admin/users/invite_token", %{"max_use" => 150}) - token = json_response(conn, 200) - invite = UserInviteToken.find_by_token!(token) + invite_json = json_response(conn, 200) + invite = UserInviteToken.find_by_token!(invite_json["token"]) refute invite.used refute invite.expires_at assert invite.max_use == 150 @@ -1117,12 +1249,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do test "with max use and expires_at", %{conn: conn} do conn = - get(conn, "/api/pleroma/admin/users/invite_token", %{ - "invite" => %{"max_use" => 150, "expires_at" => Date.to_string(Date.utc_today())} + post(conn, "/api/pleroma/admin/users/invite_token", %{ + "max_use" => 150, + "expires_at" => Date.to_string(Date.utc_today()) }) - token = json_response(conn, 200) - invite = UserInviteToken.find_by_token!(token) + invite_json = json_response(conn, 200) + invite = UserInviteToken.find_by_token!(invite_json["token"]) refute invite.used assert invite.expires_at == Date.utc_today() assert invite.max_use == 150 @@ -1131,16 +1264,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do end describe "GET /api/pleroma/admin/users/invites" do - setup do - admin = insert(:user, info: %{is_admin: true}) - - conn = - build_conn() - |> assign(:user, admin) - - {:ok, conn: conn} - end - test "no invites", %{conn: conn} do conn = get(conn, "/api/pleroma/admin/users/invites") @@ -1169,14 +1292,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do end describe "POST /api/pleroma/admin/users/revoke_invite" do - test "with token" do - admin = insert(:user, info: %{is_admin: true}) + test "with token", %{conn: conn} do {:ok, invite} = UserInviteToken.create_invite() - conn = - build_conn() - |> assign(:user, admin) - |> post("/api/pleroma/admin/users/revoke_invite", %{"token" => invite.token}) + conn = post(conn, "/api/pleroma/admin/users/revoke_invite", %{"token" => invite.token}) assert json_response(conn, 200) == %{ "expires_at" => nil, @@ -1189,25 +1308,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do } end - test "with invalid token" do - admin = insert(:user, info: %{is_admin: true}) - - conn = - build_conn() - |> assign(:user, admin) - |> post("/api/pleroma/admin/users/revoke_invite", %{"token" => "foo"}) + test "with invalid token", %{conn: conn} do + conn = post(conn, "/api/pleroma/admin/users/revoke_invite", %{"token" => "foo"}) assert json_response(conn, :not_found) == "Not found" end end describe "GET /api/pleroma/admin/reports/:id" do - setup %{conn: conn} do - admin = insert(:user, info: %{is_admin: true}) - - %{conn: assign(conn, :user, admin)} - end - test "returns report by its id", %{conn: conn} do [reporter, target_user] = insert_pair(:user) activity = insert(:note_activity, user: target_user) @@ -1234,9 +1342,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do end end - describe "PUT /api/pleroma/admin/reports/:id" do - setup %{conn: conn} do - admin = insert(:user, info: %{is_admin: true}) + describe "PATCH /api/pleroma/admin/reports" do + setup do [reporter, target_user] = insert_pair(:user) activity = insert(:note_activity, user: target_user) @@ -1247,16 +1354,54 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "status_ids" => [activity.id] }) - %{conn: assign(conn, :user, admin), id: report_id, admin: admin} + {:ok, %{id: second_report_id}} = + CommonAPI.report(reporter, %{ + "account_id" => target_user.id, + "comment" => "I feel very offended", + "status_ids" => [activity.id] + }) + + %{ + id: report_id, + second_report_id: second_report_id + } end - test "mark report as resolved", %{conn: conn, id: id, admin: admin} do + test "requires admin:write:reports scope", %{conn: conn, id: id, admin: admin} do + read_token = insert(:oauth_token, user: admin, scopes: ["admin:read"]) + write_token = insert(:oauth_token, user: admin, scopes: ["admin:write:reports"]) + response = conn - |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "resolved"}) - |> json_response(:ok) + |> assign(:token, read_token) + |> patch("/api/pleroma/admin/reports", %{ + "reports" => [%{"state" => "resolved", "id" => id}] + }) + |> json_response(403) - assert response["state"] == "resolved" + assert response == %{ + "error" => "Insufficient permissions: admin:write:reports." + } + + conn + |> assign(:token, write_token) + |> patch("/api/pleroma/admin/reports", %{ + "reports" => [%{"state" => "resolved", "id" => id}] + }) + |> json_response(:no_content) + end + + test "mark report as resolved", %{conn: conn, id: id, admin: admin} do + conn + |> patch("/api/pleroma/admin/reports", %{ + "reports" => [ + %{"state" => "resolved", "id" => id} + ] + }) + |> json_response(:no_content) + + activity = Activity.get_by_id(id) + assert activity.data["state"] == "resolved" log_entry = Repo.one(ModerationLog) @@ -1265,12 +1410,16 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do end test "closes report", %{conn: conn, id: id, admin: admin} do - response = - conn - |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "closed"}) - |> json_response(:ok) + conn + |> patch("/api/pleroma/admin/reports", %{ + "reports" => [ + %{"state" => "closed", "id" => id} + ] + }) + |> json_response(:no_content) - assert response["state"] == "closed" + activity = Activity.get_by_id(id) + assert activity.data["state"] == "closed" log_entry = Repo.one(ModerationLog) @@ -1281,27 +1430,58 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do test "returns 400 when state is unknown", %{conn: conn, id: id} do conn = conn - |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "test"}) + |> patch("/api/pleroma/admin/reports", %{ + "reports" => [ + %{"state" => "test", "id" => id} + ] + }) - assert json_response(conn, :bad_request) == "Unsupported state" + assert hd(json_response(conn, :bad_request))["error"] == "Unsupported state" end test "returns 404 when report is not exist", %{conn: conn} do conn = conn - |> put("/api/pleroma/admin/reports/test", %{"state" => "closed"}) + |> patch("/api/pleroma/admin/reports", %{ + "reports" => [ + %{"state" => "closed", "id" => "test"} + ] + }) - assert json_response(conn, :not_found) == "Not found" + assert hd(json_response(conn, :bad_request))["error"] == "not_found" end - end - describe "GET /api/pleroma/admin/reports" do - setup %{conn: conn} do - admin = insert(:user, info: %{is_admin: true}) + test "updates state of multiple reports", %{ + conn: conn, + id: id, + admin: admin, + second_report_id: second_report_id + } do + conn + |> patch("/api/pleroma/admin/reports", %{ + "reports" => [ + %{"state" => "resolved", "id" => id}, + %{"state" => "closed", "id" => second_report_id} + ] + }) + |> json_response(:no_content) - %{conn: assign(conn, :user, admin)} + activity = Activity.get_by_id(id) + second_activity = Activity.get_by_id(second_report_id) + assert activity.data["state"] == "resolved" + assert second_activity.data["state"] == "closed" + + [first_log_entry, second_log_entry] = Repo.all(ModerationLog) + + assert ModerationLog.get_log_entry_message(first_log_entry) == + "@#{admin.nickname} updated report ##{id} with 'resolved' state" + + assert ModerationLog.get_log_entry_message(second_log_entry) == + "@#{admin.nickname} updated report ##{second_report_id} with 'closed' state" end + end + describe "GET /api/pleroma/admin/reports" do test "returns empty response when no reports created", %{conn: conn} do response = conn @@ -1309,6 +1489,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do |> json_response(:ok) assert Enum.empty?(response["reports"]) + assert response["total"] == 0 end test "returns reports", %{conn: conn} do @@ -1331,6 +1512,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do assert length(response["reports"]) == 1 assert report["id"] == report_id + + assert response["total"] == 1 end test "returns reports with specified state", %{conn: conn} do @@ -1364,6 +1547,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do assert length(response["reports"]) == 1 assert open_report["id"] == first_report_id + assert response["total"] == 1 + response = conn |> get("/api/pleroma/admin/reports", %{ @@ -1376,6 +1561,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do assert length(response["reports"]) == 1 assert closed_report["id"] == second_report_id + assert response["total"] == 1 + response = conn |> get("/api/pleroma/admin/reports", %{ @@ -1384,90 +1571,237 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do |> json_response(:ok) assert Enum.empty?(response["reports"]) + assert response["total"] == 0 end test "returns 403 when requested by a non-admin" do user = insert(:user) + token = insert(:oauth_token, user: user) conn = build_conn() |> assign(:user, user) + |> assign(:token, token) |> get("/api/pleroma/admin/reports") - assert json_response(conn, :forbidden) == %{"error" => "User is not admin."} + assert json_response(conn, :forbidden) == + %{"error" => "User is not an admin or OAuth admin scope is not granted."} end test "returns 403 when requested by anonymous" do - conn = - build_conn() - |> get("/api/pleroma/admin/reports") + conn = get(build_conn(), "/api/pleroma/admin/reports") assert json_response(conn, :forbidden) == %{"error" => "Invalid credentials."} end end - # - describe "POST /api/pleroma/admin/reports/:id/respond" do - setup %{conn: conn} do - admin = insert(:user, info: %{is_admin: true}) + describe "GET /api/pleroma/admin/grouped_reports" do + setup do + [reporter, target_user] = insert_pair(:user) - %{conn: assign(conn, :user, admin), admin: admin} - end + date1 = (DateTime.to_unix(DateTime.utc_now()) + 1000) |> DateTime.from_unix!() + date2 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!() + date3 = (DateTime.to_unix(DateTime.utc_now()) + 3000) |> DateTime.from_unix!() - test "returns created dm", %{conn: conn, admin: admin} do - [reporter, target_user] = insert_pair(:user) - activity = insert(:note_activity, user: target_user) + first_status = + insert(:note_activity, user: target_user, data_attrs: %{"published" => date1}) - {:ok, %{id: report_id}} = + second_status = + insert(:note_activity, user: target_user, data_attrs: %{"published" => date2}) + + third_status = + insert(:note_activity, user: target_user, data_attrs: %{"published" => date3}) + + {:ok, first_report} = CommonAPI.report(reporter, %{ "account_id" => target_user.id, - "comment" => "I feel offended", - "status_ids" => [activity.id] + "status_ids" => [first_status.id, second_status.id, third_status.id] + }) + + {:ok, second_report} = + CommonAPI.report(reporter, %{ + "account_id" => target_user.id, + "status_ids" => [first_status.id, second_status.id] + }) + + {:ok, third_report} = + CommonAPI.report(reporter, %{ + "account_id" => target_user.id, + "status_ids" => [first_status.id] }) + %{ + first_status: Activity.get_by_ap_id_with_object(first_status.data["id"]), + second_status: Activity.get_by_ap_id_with_object(second_status.data["id"]), + third_status: Activity.get_by_ap_id_with_object(third_status.data["id"]), + first_report: first_report, + first_status_reports: [first_report, second_report, third_report], + second_status_reports: [first_report, second_report], + third_status_reports: [first_report], + target_user: target_user, + reporter: reporter + } + end + + test "returns reports grouped by status", %{ + conn: conn, + first_status: first_status, + second_status: second_status, + third_status: third_status, + first_status_reports: first_status_reports, + second_status_reports: second_status_reports, + third_status_reports: third_status_reports, + target_user: target_user, + reporter: reporter + } do response = conn - |> post("/api/pleroma/admin/reports/#{report_id}/respond", %{ - "status" => "I will check it out" - }) + |> get("/api/pleroma/admin/grouped_reports") |> json_response(:ok) - recipients = Enum.map(response["mentions"], & &1["username"]) + assert length(response["reports"]) == 3 - assert reporter.nickname in recipients - assert response["content"] == "I will check it out" - assert response["visibility"] == "direct" + first_group = Enum.find(response["reports"], &(&1["status"]["id"] == first_status.id)) - log_entry = Repo.one(ModerationLog) + second_group = Enum.find(response["reports"], &(&1["status"]["id"] == second_status.id)) - assert ModerationLog.get_log_entry_message(log_entry) == - "@#{admin.nickname} responded with 'I will check it out' to report ##{ - response["id"] - }" + third_group = Enum.find(response["reports"], &(&1["status"]["id"] == third_status.id)) + + assert length(first_group["reports"]) == 3 + assert length(second_group["reports"]) == 2 + assert length(third_group["reports"]) == 1 + + assert first_group["date"] == + Enum.max_by(first_status_reports, fn act -> + NaiveDateTime.from_iso8601!(act.data["published"]) + end).data["published"] + + assert first_group["status"] == + Map.put( + stringify_keys(StatusView.render("show.json", %{activity: first_status})), + "deleted", + false + ) + + assert(first_group["account"]["id"] == target_user.id) + + assert length(first_group["actors"]) == 1 + assert hd(first_group["actors"])["id"] == reporter.id + + assert Enum.map(first_group["reports"], & &1["id"]) -- + Enum.map(first_status_reports, & &1.id) == [] + + assert second_group["date"] == + Enum.max_by(second_status_reports, fn act -> + NaiveDateTime.from_iso8601!(act.data["published"]) + end).data["published"] + + assert second_group["status"] == + Map.put( + stringify_keys(StatusView.render("show.json", %{activity: second_status})), + "deleted", + false + ) + + assert second_group["account"]["id"] == target_user.id + + assert length(second_group["actors"]) == 1 + assert hd(second_group["actors"])["id"] == reporter.id + + assert Enum.map(second_group["reports"], & &1["id"]) -- + Enum.map(second_status_reports, & &1.id) == [] + + assert third_group["date"] == + Enum.max_by(third_status_reports, fn act -> + NaiveDateTime.from_iso8601!(act.data["published"]) + end).data["published"] + + assert third_group["status"] == + Map.put( + stringify_keys(StatusView.render("show.json", %{activity: third_status})), + "deleted", + false + ) + + assert third_group["account"]["id"] == target_user.id + + assert length(third_group["actors"]) == 1 + assert hd(third_group["actors"])["id"] == reporter.id + + assert Enum.map(third_group["reports"], & &1["id"]) -- + Enum.map(third_status_reports, & &1.id) == [] end - test "returns 400 when status is missing", %{conn: conn} do - conn = post(conn, "/api/pleroma/admin/reports/test/respond") + test "reopened report renders status data", %{ + conn: conn, + first_report: first_report, + first_status: first_status + } do + {:ok, _} = CommonAPI.update_report_state(first_report.id, "resolved") - assert json_response(conn, :bad_request) == "Invalid parameters" + response = + conn + |> get("/api/pleroma/admin/grouped_reports") + |> json_response(:ok) + + first_group = Enum.find(response["reports"], &(&1["status"]["id"] == first_status.id)) + + assert first_group["status"] == + Map.put( + stringify_keys(StatusView.render("show.json", %{activity: first_status})), + "deleted", + false + ) end - test "returns 404 when report id is invalid", %{conn: conn} do - conn = - post(conn, "/api/pleroma/admin/reports/test/respond", %{ - "status" => "foo" - }) + test "reopened report does not render status data if status has been deleted", %{ + conn: conn, + first_report: first_report, + first_status: first_status, + target_user: target_user + } do + {:ok, _} = CommonAPI.update_report_state(first_report.id, "resolved") + {:ok, _} = CommonAPI.delete(first_status.id, target_user) - assert json_response(conn, :not_found) == "Not found" + refute Activity.get_by_ap_id(first_status.id) + + response = + conn + |> get("/api/pleroma/admin/grouped_reports") + |> json_response(:ok) + + assert Enum.find(response["reports"], &(&1["status"]["deleted"] == true))["status"][ + "deleted" + ] == true + + assert length(Enum.filter(response["reports"], &(&1["status"]["deleted"] == false))) == 2 + end + + test "account not empty if status was deleted", %{ + conn: conn, + first_report: first_report, + first_status: first_status, + target_user: target_user + } do + {:ok, _} = CommonAPI.update_report_state(first_report.id, "resolved") + {:ok, _} = CommonAPI.delete(first_status.id, target_user) + + refute Activity.get_by_ap_id(first_status.id) + + response = + conn + |> get("/api/pleroma/admin/grouped_reports") + |> json_response(:ok) + + assert Enum.find(response["reports"], &(&1["status"]["deleted"] == true))["account"] end end describe "PUT /api/pleroma/admin/statuses/:id" do - setup %{conn: conn} do - admin = insert(:user, info: %{is_admin: true}) + setup do activity = insert(:note_activity) - %{conn: assign(conn, :user, admin), id: activity.id, admin: admin} + %{id: activity.id} end test "toggle sensitive flag", %{conn: conn, id: id, admin: admin} do @@ -1520,20 +1854,17 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do end test "returns 400 when visibility is unknown", %{conn: conn, id: id} do - conn = - conn - |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "test"}) + conn = put(conn, "/api/pleroma/admin/statuses/#{id}", %{"visibility" => "test"}) assert json_response(conn, :bad_request) == "Unsupported visibility" end end describe "DELETE /api/pleroma/admin/statuses/:id" do - setup %{conn: conn} do - admin = insert(:user, info: %{is_admin: true}) + setup do activity = insert(:note_activity) - %{conn: assign(conn, :user, admin), id: activity.id, admin: admin} + %{id: activity.id} end test "deletes status", %{conn: conn, id: id, admin: admin} do @@ -1549,41 +1880,41 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "@#{admin.nickname} deleted status ##{id}" end - test "returns error when status is not exist", %{conn: conn} do - conn = - conn - |> delete("/api/pleroma/admin/statuses/test") + test "returns 404 when the status does not exist", %{conn: conn} do + conn = delete(conn, "/api/pleroma/admin/statuses/test") - assert json_response(conn, :bad_request) == "Could not delete" + assert json_response(conn, :not_found) == "Not found" end end describe "GET /api/pleroma/admin/config" do - setup %{conn: conn} do - admin = insert(:user, info: %{is_admin: true}) - - %{conn: assign(conn, :user, admin)} + clear_config(:configurable_from_database) do + Config.put(:configurable_from_database, true) end - test "without any settings in db", %{conn: conn} do + test "when configuration from database is off", %{conn: conn} do + Config.put(:configurable_from_database, false) conn = get(conn, "/api/pleroma/admin/config") - assert json_response(conn, 200) == %{"configs" => []} + assert json_response(conn, 400) == + "To use this endpoint you need to enable configuration from database." end - test "with settings in db", %{conn: conn} do + test "with settings only in db", %{conn: conn} do config1 = insert(:config) config2 = insert(:config) - conn = get(conn, "/api/pleroma/admin/config") + conn = get(conn, "/api/pleroma/admin/config", %{"only_db" => true}) %{ "configs" => [ %{ + "group" => ":pleroma", "key" => key1, "value" => _ }, %{ + "group" => ":pleroma", "key" => key2, "value" => _ } @@ -1593,13 +1924,107 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do assert key1 == config1.key assert key2 == config2.key end + + test "db is added to settings that are in db", %{conn: conn} do + _config = insert(:config, key: ":instance", value: ConfigDB.to_binary(name: "Some name")) + + %{"configs" => configs} = + conn + |> get("/api/pleroma/admin/config") + |> json_response(200) + + [instance_config] = + Enum.filter(configs, fn %{"group" => group, "key" => key} -> + group == ":pleroma" and key == ":instance" + end) + + assert instance_config["db"] == [":name"] + end + + test "merged default setting with db settings", %{conn: conn} do + config1 = insert(:config) + config2 = insert(:config) + + config3 = + insert(:config, + value: ConfigDB.to_binary(k1: :v1, k2: :v2) + ) + + %{"configs" => configs} = + conn + |> get("/api/pleroma/admin/config") + |> json_response(200) + + assert length(configs) > 3 + + received_configs = + Enum.filter(configs, fn %{"group" => group, "key" => key} -> + group == ":pleroma" and key in [config1.key, config2.key, config3.key] + end) + + assert length(received_configs) == 3 + + db_keys = + config3.value + |> ConfigDB.from_binary() + |> Keyword.keys() + |> ConfigDB.convert() + + Enum.each(received_configs, fn %{"value" => value, "db" => db} -> + assert db in [[config1.key], [config2.key], db_keys] + + assert value in [ + ConfigDB.from_binary_with_convert(config1.value), + ConfigDB.from_binary_with_convert(config2.value), + ConfigDB.from_binary_with_convert(config3.value) + ] + end) + end + + test "subkeys with full update right merge", %{conn: conn} do + config1 = + insert(:config, + key: ":emoji", + value: ConfigDB.to_binary(groups: [a: 1, b: 2], key: [a: 1]) + ) + + config2 = + insert(:config, + key: ":assets", + value: ConfigDB.to_binary(mascots: [a: 1, b: 2], key: [a: 1]) + ) + + %{"configs" => configs} = + conn + |> get("/api/pleroma/admin/config") + |> json_response(200) + + vals = + Enum.filter(configs, fn %{"group" => group, "key" => key} -> + group == ":pleroma" and key in [config1.key, config2.key] + end) + + emoji = Enum.find(vals, fn %{"key" => key} -> key == ":emoji" end) + assets = Enum.find(vals, fn %{"key" => key} -> key == ":assets" end) + + emoji_val = ConfigDB.transform_with_out_binary(emoji["value"]) + assets_val = ConfigDB.transform_with_out_binary(assets["value"]) + + assert emoji_val[:groups] == [a: 1, b: 2] + assert assets_val[:mascots] == [a: 1, b: 2] + end end - describe "POST /api/pleroma/admin/config" do - setup %{conn: conn} do - admin = insert(:user, info: %{is_admin: true}) + test "POST /api/pleroma/admin/config error", %{conn: conn} do + conn = post(conn, "/api/pleroma/admin/config", %{"configs" => []}) - temp_file = "config/test.exported_from_db.secret.exs" + assert json_response(conn, 400) == + "To use this endpoint you need to enable configuration from database." + end + + describe "POST /api/pleroma/admin/config" do + setup do + http = Application.get_env(:pleroma, :http) on_exit(fn -> Application.delete_env(:pleroma, :key1) @@ -1610,29 +2035,33 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do Application.delete_env(:pleroma, :keyaa2) Application.delete_env(:pleroma, Pleroma.Web.Endpoint.NotReal) Application.delete_env(:pleroma, Pleroma.Captcha.NotReal) - :ok = File.rm(temp_file) + Application.put_env(:pleroma, :http, http) + Application.put_env(:tesla, :adapter, Tesla.Mock) + Restarter.Pleroma.refresh() end) - - %{conn: assign(conn, :user, admin)} end - clear_config([:instance, :dynamic_configuration]) do - Pleroma.Config.put([:instance, :dynamic_configuration], true) + clear_config(:configurable_from_database) do + Config.put(:configurable_from_database, true) end + @tag capture_log: true test "create new config setting in db", %{conn: conn} do + ueberauth = Application.get_env(:ueberauth, Ueberauth) + on_exit(fn -> Application.put_env(:ueberauth, Ueberauth, ueberauth) end) + conn = post(conn, "/api/pleroma/admin/config", %{ configs: [ - %{group: "pleroma", key: "key1", value: "value1"}, + %{group: ":pleroma", key: ":key1", value: "value1"}, %{ - group: "ueberauth", - key: "Ueberauth.Strategy.Twitter.OAuth", + group: ":ueberauth", + key: "Ueberauth", value: [%{"tuple" => [":consumer_secret", "aaaa"]}] }, %{ - group: "pleroma", - key: "key2", + group: ":pleroma", + key: ":key2", value: %{ ":nested_1" => "nested_value1", ":nested_2" => [ @@ -1642,21 +2071,21 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do } }, %{ - group: "pleroma", - key: "key3", + group: ":pleroma", + key: ":key3", value: [ %{"nested_3" => ":nested_3", "nested_33" => "nested_33"}, %{"nested_4" => true} ] }, %{ - group: "pleroma", - key: "key4", + group: ":pleroma", + key: ":key4", value: %{":nested_5" => ":upload", "endpoint" => "https://example.com"} }, %{ - group: "idna", - key: "key5", + group: ":idna", + key: ":key5", value: %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]} } ] @@ -1665,43 +2094,49 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do assert json_response(conn, 200) == %{ "configs" => [ %{ - "group" => "pleroma", - "key" => "key1", - "value" => "value1" + "group" => ":pleroma", + "key" => ":key1", + "value" => "value1", + "db" => [":key1"] }, %{ - "group" => "ueberauth", - "key" => "Ueberauth.Strategy.Twitter.OAuth", - "value" => [%{"tuple" => [":consumer_secret", "aaaa"]}] + "group" => ":ueberauth", + "key" => "Ueberauth", + "value" => [%{"tuple" => [":consumer_secret", "aaaa"]}], + "db" => [":consumer_secret"] }, %{ - "group" => "pleroma", - "key" => "key2", + "group" => ":pleroma", + "key" => ":key2", "value" => %{ ":nested_1" => "nested_value1", ":nested_2" => [ %{":nested_22" => "nested_value222"}, %{":nested_33" => %{":nested_44" => "nested_444"}} ] - } + }, + "db" => [":key2"] }, %{ - "group" => "pleroma", - "key" => "key3", + "group" => ":pleroma", + "key" => ":key3", "value" => [ %{"nested_3" => ":nested_3", "nested_33" => "nested_33"}, %{"nested_4" => true} - ] + ], + "db" => [":key3"] }, %{ - "group" => "pleroma", - "key" => "key4", - "value" => %{"endpoint" => "https://example.com", ":nested_5" => ":upload"} + "group" => ":pleroma", + "key" => ":key4", + "value" => %{"endpoint" => "https://example.com", ":nested_5" => ":upload"}, + "db" => [":key4"] }, %{ - "group" => "idna", - "key" => "key5", - "value" => %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]} + "group" => ":idna", + "key" => ":key5", + "value" => %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]}, + "db" => [":key5"] } ] } @@ -1729,25 +2164,303 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do assert Application.get_env(:idna, :key5) == {"string", Pleroma.Captcha.NotReal, []} end - test "update config setting & delete", %{conn: conn} do - config1 = insert(:config, key: "keyaa1") - config2 = insert(:config, key: "keyaa2") + test "save configs setting without explicit key", %{conn: conn} do + level = Application.get_env(:quack, :level) + meta = Application.get_env(:quack, :meta) + webhook_url = Application.get_env(:quack, :webhook_url) - insert(:config, - group: "ueberauth", - key: "Ueberauth.Strategy.Microsoft.OAuth", - value: :erlang.term_to_binary([]) - ) + on_exit(fn -> + Application.put_env(:quack, :level, level) + Application.put_env(:quack, :meta, meta) + Application.put_env(:quack, :webhook_url, webhook_url) + end) + + conn = + post(conn, "/api/pleroma/admin/config", %{ + configs: [ + %{ + group: ":quack", + key: ":level", + value: ":info" + }, + %{ + group: ":quack", + key: ":meta", + value: [":none"] + }, + %{ + group: ":quack", + key: ":webhook_url", + value: "https://hooks.slack.com/services/KEY" + } + ] + }) + + assert json_response(conn, 200) == %{ + "configs" => [ + %{ + "group" => ":quack", + "key" => ":level", + "value" => ":info", + "db" => [":level"] + }, + %{ + "group" => ":quack", + "key" => ":meta", + "value" => [":none"], + "db" => [":meta"] + }, + %{ + "group" => ":quack", + "key" => ":webhook_url", + "value" => "https://hooks.slack.com/services/KEY", + "db" => [":webhook_url"] + } + ] + } + + assert Application.get_env(:quack, :level) == :info + assert Application.get_env(:quack, :meta) == [:none] + assert Application.get_env(:quack, :webhook_url) == "https://hooks.slack.com/services/KEY" + end + + test "saving config with partial update", %{conn: conn} do + config = insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: 2)) + + conn = + post(conn, "/api/pleroma/admin/config", %{ + configs: [ + %{group: config.group, key: config.key, value: [%{"tuple" => [":key3", 3]}]} + ] + }) + + assert json_response(conn, 200) == %{ + "configs" => [ + %{ + "group" => ":pleroma", + "key" => ":key1", + "value" => [ + %{"tuple" => [":key1", 1]}, + %{"tuple" => [":key2", 2]}, + %{"tuple" => [":key3", 3]} + ], + "db" => [":key1", ":key2", ":key3"] + } + ] + } + end + + test "saving config which need pleroma reboot", %{conn: conn} do + chat = Config.get(:chat) + on_exit(fn -> Config.put(:chat, chat) end) + + assert post( + conn, + "/api/pleroma/admin/config", + %{ + configs: [ + %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]} + ] + } + ) + |> json_response(200) == %{ + "configs" => [ + %{ + "db" => [":enabled"], + "group" => ":pleroma", + "key" => ":chat", + "value" => [%{"tuple" => [":enabled", true]}] + } + ], + "need_reboot" => true + } + + configs = + conn + |> get("/api/pleroma/admin/config") + |> json_response(200) + + assert configs["need_reboot"] + + capture_log(fn -> + assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{} + end) =~ "pleroma restarted" + + configs = + conn + |> get("/api/pleroma/admin/config") + |> json_response(200) + + refute Map.has_key?(configs, "need_reboot") + end + + test "update setting which need reboot, don't change reboot flag until reboot", %{conn: conn} do + chat = Config.get(:chat) + on_exit(fn -> Config.put(:chat, chat) end) + + assert post( + conn, + "/api/pleroma/admin/config", + %{ + configs: [ + %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]} + ] + } + ) + |> json_response(200) == %{ + "configs" => [ + %{ + "db" => [":enabled"], + "group" => ":pleroma", + "key" => ":chat", + "value" => [%{"tuple" => [":enabled", true]}] + } + ], + "need_reboot" => true + } + + assert post(conn, "/api/pleroma/admin/config", %{ + configs: [ + %{group: ":pleroma", key: ":key1", value: [%{"tuple" => [":key3", 3]}]} + ] + }) + |> json_response(200) == %{ + "configs" => [ + %{ + "group" => ":pleroma", + "key" => ":key1", + "value" => [ + %{"tuple" => [":key3", 3]} + ], + "db" => [":key3"] + } + ], + "need_reboot" => true + } + + capture_log(fn -> + assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{} + end) =~ "pleroma restarted" + + configs = + conn + |> get("/api/pleroma/admin/config") + |> json_response(200) + + refute Map.has_key?(configs, "need_reboot") + end + + test "saving config with nested merge", %{conn: conn} do + config = + insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: [k1: 1, k2: 2])) + + conn = + post(conn, "/api/pleroma/admin/config", %{ + configs: [ + %{ + group: config.group, + key: config.key, + value: [ + %{"tuple" => [":key3", 3]}, + %{ + "tuple" => [ + ":key2", + [ + %{"tuple" => [":k2", 1]}, + %{"tuple" => [":k3", 3]} + ] + ] + } + ] + } + ] + }) + + assert json_response(conn, 200) == %{ + "configs" => [ + %{ + "group" => ":pleroma", + "key" => ":key1", + "value" => [ + %{"tuple" => [":key1", 1]}, + %{"tuple" => [":key3", 3]}, + %{ + "tuple" => [ + ":key2", + [ + %{"tuple" => [":k1", 1]}, + %{"tuple" => [":k2", 1]}, + %{"tuple" => [":k3", 3]} + ] + ] + } + ], + "db" => [":key1", ":key3", ":key2"] + } + ] + } + end + + test "saving special atoms", %{conn: conn} do + conn = + post(conn, "/api/pleroma/admin/config", %{ + "configs" => [ + %{ + "group" => ":pleroma", + "key" => ":key1", + "value" => [ + %{ + "tuple" => [ + ":ssl_options", + [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}] + ] + } + ] + } + ] + }) + + assert json_response(conn, 200) == %{ + "configs" => [ + %{ + "group" => ":pleroma", + "key" => ":key1", + "value" => [ + %{ + "tuple" => [ + ":ssl_options", + [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}] + ] + } + ], + "db" => [":ssl_options"] + } + ] + } + + assert Application.get_env(:pleroma, :key1) == [ + ssl_options: [versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"]] + ] + end + + test "saving full setting if value is in full_key_update list", %{conn: conn} do + backends = Application.get_env(:logger, :backends) + on_exit(fn -> Application.put_env(:logger, :backends, backends) end) + + config = + insert(:config, + group: ":logger", + key: ":backends", + value: :erlang.term_to_binary([]) + ) conn = post(conn, "/api/pleroma/admin/config", %{ configs: [ - %{group: config1.group, key: config1.key, value: "another_value"}, - %{group: config2.group, key: config2.key, delete: "true"}, %{ - group: "ueberauth", - key: "Ueberauth.Strategy.Microsoft.OAuth", - delete: "true" + group: config.group, + key: config.key, + value: [":console", %{"tuple" => ["ExSyslogger", ":ex_syslogger"]}] } ] }) @@ -1755,23 +2468,131 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do assert json_response(conn, 200) == %{ "configs" => [ %{ - "group" => "pleroma", + "group" => ":logger", + "key" => ":backends", + "value" => [ + ":console", + %{"tuple" => ["ExSyslogger", ":ex_syslogger"]} + ], + "db" => [":backends"] + } + ] + } + + assert Application.get_env(:logger, :backends) == [ + :console, + {ExSyslogger, :ex_syslogger} + ] + + capture_log(fn -> + require Logger + Logger.warn("Ooops...") + end) =~ "Ooops..." + end + + test "saving full setting if value is not keyword", %{conn: conn} do + config = + insert(:config, + group: ":tesla", + key: ":adapter", + value: :erlang.term_to_binary(Tesla.Adapter.Hackey) + ) + + conn = + post(conn, "/api/pleroma/admin/config", %{ + configs: [ + %{group: config.group, key: config.key, value: "Tesla.Adapter.Httpc"} + ] + }) + + assert json_response(conn, 200) == %{ + "configs" => [ + %{ + "group" => ":tesla", + "key" => ":adapter", + "value" => "Tesla.Adapter.Httpc", + "db" => [":adapter"] + } + ] + } + end + + test "update config setting & delete with fallback to default value", %{ + conn: conn, + admin: admin, + token: token + } do + ueberauth = Application.get_env(:ueberauth, Ueberauth) + config1 = insert(:config, key: ":keyaa1") + config2 = insert(:config, key: ":keyaa2") + + config3 = + insert(:config, + group: ":ueberauth", + key: "Ueberauth" + ) + + conn = + post(conn, "/api/pleroma/admin/config", %{ + configs: [ + %{group: config1.group, key: config1.key, value: "another_value"}, + %{group: config2.group, key: config2.key, value: "another_value"} + ] + }) + + assert json_response(conn, 200) == %{ + "configs" => [ + %{ + "group" => ":pleroma", "key" => config1.key, - "value" => "another_value" + "value" => "another_value", + "db" => [":keyaa1"] + }, + %{ + "group" => ":pleroma", + "key" => config2.key, + "value" => "another_value", + "db" => [":keyaa2"] } ] } assert Application.get_env(:pleroma, :keyaa1) == "another_value" - refute Application.get_env(:pleroma, :keyaa2) + assert Application.get_env(:pleroma, :keyaa2) == "another_value" + assert Application.get_env(:ueberauth, Ueberauth) == ConfigDB.from_binary(config3.value) + + conn = + build_conn() + |> assign(:user, admin) + |> assign(:token, token) + |> post("/api/pleroma/admin/config", %{ + configs: [ + %{group: config2.group, key: config2.key, delete: true}, + %{ + group: ":ueberauth", + key: "Ueberauth", + delete: true + } + ] + }) + + assert json_response(conn, 200) == %{ + "configs" => [] + } + + assert Application.get_env(:ueberauth, Ueberauth) == ueberauth + refute Keyword.has_key?(Application.get_all_env(:pleroma), :keyaa2) end test "common config example", %{conn: conn} do + adapter = Application.get_env(:tesla, :adapter) + on_exit(fn -> Application.put_env(:tesla, :adapter, adapter) end) + conn = post(conn, "/api/pleroma/admin/config", %{ configs: [ %{ - "group" => "pleroma", + "group" => ":pleroma", "key" => "Pleroma.Captcha.NotReal", "value" => [ %{"tuple" => [":enabled", false]}, @@ -1783,16 +2604,25 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do %{"tuple" => [":regex1", "~r/https:\/\/example.com/"]}, %{"tuple" => [":regex2", "~r/https:\/\/example.com/u"]}, %{"tuple" => [":regex3", "~r/https:\/\/example.com/i"]}, - %{"tuple" => [":regex4", "~r/https:\/\/example.com/s"]} + %{"tuple" => [":regex4", "~r/https:\/\/example.com/s"]}, + %{"tuple" => [":name", "Pleroma"]} ] + }, + %{ + "group" => ":tesla", + "key" => ":adapter", + "value" => "Tesla.Adapter.Httpc" } ] }) + assert Application.get_env(:tesla, :adapter) == Tesla.Adapter.Httpc + assert Config.get([Pleroma.Captcha.NotReal, :name]) == "Pleroma" + assert json_response(conn, 200) == %{ "configs" => [ %{ - "group" => "pleroma", + "group" => ":pleroma", "key" => "Pleroma.Captcha.NotReal", "value" => [ %{"tuple" => [":enabled", false]}, @@ -1804,8 +2634,28 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do %{"tuple" => [":regex1", "~r/https:\\/\\/example.com/"]}, %{"tuple" => [":regex2", "~r/https:\\/\\/example.com/u"]}, %{"tuple" => [":regex3", "~r/https:\\/\\/example.com/i"]}, - %{"tuple" => [":regex4", "~r/https:\\/\\/example.com/s"]} + %{"tuple" => [":regex4", "~r/https:\\/\\/example.com/s"]}, + %{"tuple" => [":name", "Pleroma"]} + ], + "db" => [ + ":enabled", + ":method", + ":seconds_valid", + ":path", + ":key1", + ":partial_chain", + ":regex1", + ":regex2", + ":regex3", + ":regex4", + ":name" ] + }, + %{ + "group" => ":tesla", + "key" => ":adapter", + "value" => "Tesla.Adapter.Httpc", + "db" => [":adapter"] } ] } @@ -1816,7 +2666,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do post(conn, "/api/pleroma/admin/config", %{ configs: [ %{ - "group" => "pleroma", + "group" => ":pleroma", "key" => "Pleroma.Web.Endpoint.NotReal", "value" => [ %{ @@ -1880,7 +2730,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do assert json_response(conn, 200) == %{ "configs" => [ %{ - "group" => "pleroma", + "group" => ":pleroma", "key" => "Pleroma.Web.Endpoint.NotReal", "value" => [ %{ @@ -1936,7 +2786,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do ] ] } - ] + ], + "db" => [":http"] } ] } @@ -1947,7 +2798,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do post(conn, "/api/pleroma/admin/config", %{ configs: [ %{ - "group" => "pleroma", + "group" => ":pleroma", "key" => ":key1", "value" => [ %{"tuple" => [":key2", "some_val"]}, @@ -1977,7 +2828,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do %{ "configs" => [ %{ - "group" => "pleroma", + "group" => ":pleroma", "key" => ":key1", "value" => [ %{"tuple" => [":key2", "some_val"]}, @@ -1998,7 +2849,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do } ] } - ] + ], + "db" => [":key2", ":key3"] } ] } @@ -2009,7 +2861,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do post(conn, "/api/pleroma/admin/config", %{ configs: [ %{ - "group" => "pleroma", + "group" => ":pleroma", "key" => ":key1", "value" => %{"key" => "some_val"} } @@ -2020,83 +2872,21 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do %{ "configs" => [ %{ - "group" => "pleroma", + "group" => ":pleroma", "key" => ":key1", - "value" => %{"key" => "some_val"} + "value" => %{"key" => "some_val"}, + "db" => [":key1"] } ] } end - test "dispatch setting", %{conn: conn} do - conn = - post(conn, "/api/pleroma/admin/config", %{ - configs: [ - %{ - "group" => "pleroma", - "key" => "Pleroma.Web.Endpoint.NotReal", - "value" => [ - %{ - "tuple" => [ - ":http", - [ - %{"tuple" => [":ip", %{"tuple" => [127, 0, 0, 1]}]}, - %{"tuple" => [":dispatch", ["{:_, - [ - {\"/api/v1/streaming\", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, - {\"/websocket\", Phoenix.Endpoint.CowboyWebSocket, - {Phoenix.Transports.WebSocket, - {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: \"/websocket\"]}}}, - {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}} - ]}"]]} - ] - ] - } - ] - } - ] - }) - - dispatch_string = - "{:_, [{\"/api/v1/streaming\", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, " <> - "{\"/websocket\", Phoenix.Endpoint.CowboyWebSocket, {Phoenix.Transports.WebSocket, " <> - "{Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: \"/websocket\"]}}}, " <> - "{:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}]}" - - assert json_response(conn, 200) == %{ - "configs" => [ - %{ - "group" => "pleroma", - "key" => "Pleroma.Web.Endpoint.NotReal", - "value" => [ - %{ - "tuple" => [ - ":http", - [ - %{"tuple" => [":ip", %{"tuple" => [127, 0, 0, 1]}]}, - %{ - "tuple" => [ - ":dispatch", - [ - dispatch_string - ] - ] - } - ] - ] - } - ] - } - ] - } - end - test "queues key as atom", %{conn: conn} do conn = post(conn, "/api/pleroma/admin/config", %{ configs: [ %{ - "group" => "pleroma_job_queue", + "group" => ":oban", "key" => ":queues", "value" => [ %{"tuple" => [":federator_incoming", 50]}, @@ -2114,7 +2904,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do assert json_response(conn, 200) == %{ "configs" => [ %{ - "group" => "pleroma_job_queue", + "group" => ":oban", "key" => ":queues", "value" => [ %{"tuple" => [":federator_incoming", 50]}, @@ -2124,6 +2914,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do %{"tuple" => [":transmogrifier", 20]}, %{"tuple" => [":scheduled_activities", 10]}, %{"tuple" => [":background", 5]} + ], + "db" => [ + ":federator_incoming", + ":federator_outgoing", + ":web_push", + ":mailer", + ":transmogrifier", + ":scheduled_activities", + ":background" ] } ] @@ -2133,7 +2932,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do test "delete part of settings by atom subkeys", %{conn: conn} do config = insert(:config, - key: "keyaa1", + key: ":keyaa1", value: :erlang.term_to_binary(subkey1: "val1", subkey2: "val2", subkey3: "val3") ) @@ -2144,64 +2943,176 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do group: config.group, key: config.key, subkeys: [":subkey1", ":subkey3"], - delete: "true" + delete: true } ] }) - assert( - json_response(conn, 200) == %{ - "configs" => [ + assert json_response(conn, 200) == %{ + "configs" => [ + %{ + "group" => ":pleroma", + "key" => ":keyaa1", + "value" => [%{"tuple" => [":subkey2", "val2"]}], + "db" => [":subkey2"] + } + ] + } + end + + test "proxy tuple localhost", %{conn: conn} do + conn = + post(conn, "/api/pleroma/admin/config", %{ + configs: [ %{ - "group" => "pleroma", - "key" => "keyaa1", - "value" => [%{"tuple" => [":subkey2", "val2"]}] + group: ":pleroma", + key: ":http", + value: [ + %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]}, + %{"tuple" => [":send_user_agent", false]} + ] } ] - } - ) + }) + + assert json_response(conn, 200) == %{ + "configs" => [ + %{ + "group" => ":pleroma", + "key" => ":http", + "value" => [ + %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]}, + %{"tuple" => [":send_user_agent", false]} + ], + "db" => [":proxy_url", ":send_user_agent"] + } + ] + } end - end - describe "config mix tasks run" do - setup %{conn: conn} do - admin = insert(:user, info: %{is_admin: true}) + test "proxy tuple domain", %{conn: conn} do + conn = + post(conn, "/api/pleroma/admin/config", %{ + configs: [ + %{ + group: ":pleroma", + key: ":http", + value: [ + %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]}, + %{"tuple" => [":send_user_agent", false]} + ] + } + ] + }) + + assert json_response(conn, 200) == %{ + "configs" => [ + %{ + "group" => ":pleroma", + "key" => ":http", + "value" => [ + %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]}, + %{"tuple" => [":send_user_agent", false]} + ], + "db" => [":proxy_url", ":send_user_agent"] + } + ] + } + end + + test "proxy tuple ip", %{conn: conn} do + conn = + post(conn, "/api/pleroma/admin/config", %{ + configs: [ + %{ + group: ":pleroma", + key: ":http", + value: [ + %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]}, + %{"tuple" => [":send_user_agent", false]} + ] + } + ] + }) - temp_file = "config/test.exported_from_db.secret.exs" + assert json_response(conn, 200) == %{ + "configs" => [ + %{ + "group" => ":pleroma", + "key" => ":http", + "value" => [ + %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]}, + %{"tuple" => [":send_user_agent", false]} + ], + "db" => [":proxy_url", ":send_user_agent"] + } + ] + } + end + end - Mix.shell(Mix.Shell.Quiet) + describe "GET /api/pleroma/admin/restart" do + clear_config(:configurable_from_database) do + Config.put(:configurable_from_database, true) + end - on_exit(fn -> - Mix.shell(Mix.Shell.IO) - :ok = File.rm(temp_file) - end) + test "pleroma restarts", %{conn: conn} do + capture_log(fn -> + assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{} + end) =~ "pleroma restarted" - %{conn: assign(conn, :user, admin), admin: admin} + refute Restarter.Pleroma.need_reboot?() end + end + + describe "GET /api/pleroma/admin/statuses" do + test "returns all public, unlisted, and direct statuses", %{conn: conn, admin: admin} do + blocked = insert(:user) + user = insert(:user) + User.block(admin, blocked) + + {:ok, _} = + CommonAPI.post(user, %{"status" => "@#{admin.nickname}", "visibility" => "direct"}) - clear_config([:instance, :dynamic_configuration]) do - Pleroma.Config.put([:instance, :dynamic_configuration], true) + {:ok, _} = CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"}) + {:ok, _} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + {:ok, _} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"}) + {:ok, _} = CommonAPI.post(blocked, %{"status" => ".", "visibility" => "public"}) + + response = + conn + |> get("/api/pleroma/admin/statuses") + |> json_response(200) + + refute "private" in Enum.map(response, & &1["visibility"]) + assert length(response) == 4 end - test "transfer settings to DB and to file", %{conn: conn, admin: admin} do - assert Pleroma.Repo.all(Pleroma.Web.AdminAPI.Config) == [] - conn = get(conn, "/api/pleroma/admin/config/migrate_to_db") - assert json_response(conn, 200) == %{} - assert Pleroma.Repo.all(Pleroma.Web.AdminAPI.Config) > 0 + test "returns only local statuses with local_only on", %{conn: conn} do + user = insert(:user) + remote_user = insert(:user, local: false, nickname: "archaeme@archae.me") + insert(:note_activity, user: user, local: true) + insert(:note_activity, user: remote_user, local: false) - conn = - build_conn() - |> assign(:user, admin) - |> get("/api/pleroma/admin/config/migrate_from_db") + response = + conn + |> get("/api/pleroma/admin/statuses?local_only=true") + |> json_response(200) + + assert length(response) == 1 + end - assert json_response(conn, 200) == %{} - assert Pleroma.Repo.all(Pleroma.Web.AdminAPI.Config) == [] + test "returns private statuses with godmode on", %{conn: conn} do + user = insert(:user) + {:ok, _} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + {:ok, _} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"}) + conn = get(conn, "/api/pleroma/admin/statuses?godmode=true") + assert json_response(conn, 200) |> length() == 2 end end describe "GET /api/pleroma/admin/users/:nickname/statuses" do setup do - admin = insert(:user, info: %{is_admin: true}) user = insert(:user) date1 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!() @@ -2212,11 +3123,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do insert(:note_activity, user: user, published: date2) insert(:note_activity, user: user, published: date3) - conn = - build_conn() - |> assign(:user, admin) - - {:ok, conn: conn, user: user} + %{user: user} end test "renders user's statuses", %{conn: conn, user: user} do @@ -2254,13 +3161,27 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do assert json_response(conn, 200) |> length() == 5 end + + test "excludes reblogs by default", %{conn: conn, user: user} do + other_user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "."}) + {:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, other_user) + + conn_res = get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses") + assert json_response(conn_res, 200) |> length() == 0 + + conn_res = + get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses?with_reblogs=true") + + assert json_response(conn_res, 200) |> length() == 1 + end end describe "GET /api/pleroma/admin/moderation_log" do - setup %{conn: conn} do - admin = insert(:user, info: %{is_admin: true}) + setup do + moderator = insert(:user, is_moderator: true) - %{conn: assign(conn, :user, admin), admin: admin} + %{moderator: moderator} end test "returns the log", %{conn: conn, admin: admin} do @@ -2293,9 +3214,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do conn = get(conn, "/api/pleroma/admin/moderation_log") response = json_response(conn, 200) - [first_entry, second_entry] = response + [first_entry, second_entry] = response["items"] - assert response |> length() == 2 + assert response["total"] == 2 assert first_entry["data"]["action"] == "relay_unfollow" assert first_entry["message"] == @@ -2337,9 +3258,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do conn1 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=1") response1 = json_response(conn1, 200) - [first_entry] = response1 + [first_entry] = response1["items"] - assert response1 |> length() == 1 + assert response1["total"] == 2 + assert response1["items"] |> length() == 1 assert first_entry["data"]["action"] == "relay_unfollow" assert first_entry["message"] == @@ -2348,14 +3270,369 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do conn2 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=2") response2 = json_response(conn2, 200) - [second_entry] = response2 + [second_entry] = response2["items"] - assert response2 |> length() == 1 + assert response2["total"] == 2 + assert response2["items"] |> length() == 1 assert second_entry["data"]["action"] == "relay_follow" assert second_entry["message"] == "@#{admin.nickname} followed relay: https://example.org/relay" end + + test "filters log by date", %{conn: conn, admin: admin} do + first_date = "2017-08-15T15:47:06Z" + second_date = "2017-08-20T15:47:06Z" + + Repo.insert(%ModerationLog{ + data: %{ + actor: %{ + "id" => admin.id, + "nickname" => admin.nickname, + "type" => "user" + }, + action: "relay_follow", + target: "https://example.org/relay" + }, + inserted_at: NaiveDateTime.from_iso8601!(first_date) + }) + + Repo.insert(%ModerationLog{ + data: %{ + actor: %{ + "id" => admin.id, + "nickname" => admin.nickname, + "type" => "user" + }, + action: "relay_unfollow", + target: "https://example.org/relay" + }, + inserted_at: NaiveDateTime.from_iso8601!(second_date) + }) + + conn1 = + get( + conn, + "/api/pleroma/admin/moderation_log?start_date=#{second_date}" + ) + + response1 = json_response(conn1, 200) + [first_entry] = response1["items"] + + assert response1["total"] == 1 + assert first_entry["data"]["action"] == "relay_unfollow" + + assert first_entry["message"] == + "@#{admin.nickname} unfollowed relay: https://example.org/relay" + end + + test "returns log filtered by user", %{conn: conn, admin: admin, moderator: moderator} do + Repo.insert(%ModerationLog{ + data: %{ + actor: %{ + "id" => admin.id, + "nickname" => admin.nickname, + "type" => "user" + }, + action: "relay_follow", + target: "https://example.org/relay" + } + }) + + Repo.insert(%ModerationLog{ + data: %{ + actor: %{ + "id" => moderator.id, + "nickname" => moderator.nickname, + "type" => "user" + }, + action: "relay_unfollow", + target: "https://example.org/relay" + } + }) + + conn1 = get(conn, "/api/pleroma/admin/moderation_log?user_id=#{moderator.id}") + + response1 = json_response(conn1, 200) + [first_entry] = response1["items"] + + assert response1["total"] == 1 + assert get_in(first_entry, ["data", "actor", "id"]) == moderator.id + end + + test "returns log filtered by search", %{conn: conn, moderator: moderator} do + ModerationLog.insert_log(%{ + actor: moderator, + action: "relay_follow", + target: "https://example.org/relay" + }) + + ModerationLog.insert_log(%{ + actor: moderator, + action: "relay_unfollow", + target: "https://example.org/relay" + }) + + conn1 = get(conn, "/api/pleroma/admin/moderation_log?search=unfo") + + response1 = json_response(conn1, 200) + [first_entry] = response1["items"] + + assert response1["total"] == 1 + + assert get_in(first_entry, ["data", "message"]) == + "@#{moderator.nickname} unfollowed relay: https://example.org/relay" + end + end + + describe "PATCH /users/:nickname/force_password_reset" do + test "sets password_reset_pending to true", %{conn: conn} do + user = insert(:user) + assert user.password_reset_pending == false + + conn = + patch(conn, "/api/pleroma/admin/users/force_password_reset", %{nicknames: [user.nickname]}) + + assert json_response(conn, 204) == "" + + ObanHelpers.perform_all() + + assert User.get_by_id(user.id).password_reset_pending == true + end + end + + describe "relays" do + test "POST /relay", %{conn: conn, admin: admin} do + conn = + post(conn, "/api/pleroma/admin/relay", %{ + relay_url: "http://mastodon.example.org/users/admin" + }) + + assert json_response(conn, 200) == "http://mastodon.example.org/users/admin" + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin" + end + + test "GET /relay", %{conn: conn} do + relay_user = Pleroma.Web.ActivityPub.Relay.get_actor() + + ["http://mastodon.example.org/users/admin", "https://mstdn.io/users/mayuutann"] + |> Enum.each(fn ap_id -> + {:ok, user} = User.get_or_fetch_by_ap_id(ap_id) + User.follow(relay_user, user) + end) + + conn = get(conn, "/api/pleroma/admin/relay") + + assert json_response(conn, 200)["relays"] -- ["mastodon.example.org", "mstdn.io"] == [] + end + + test "DELETE /relay", %{conn: conn, admin: admin} do + post(conn, "/api/pleroma/admin/relay", %{ + relay_url: "http://mastodon.example.org/users/admin" + }) + + conn = + delete(conn, "/api/pleroma/admin/relay", %{ + relay_url: "http://mastodon.example.org/users/admin" + }) + + assert json_response(conn, 200) == "http://mastodon.example.org/users/admin" + + [log_entry_one, log_entry_two] = Repo.all(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry_one) == + "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin" + + assert ModerationLog.get_log_entry_message(log_entry_two) == + "@#{admin.nickname} unfollowed relay: http://mastodon.example.org/users/admin" + end + end + + describe "instances" do + test "GET /instances/:instance/statuses", %{conn: conn} do + user = insert(:user, local: false, nickname: "archaeme@archae.me") + user2 = insert(:user, local: false, nickname: "test@test.com") + insert_pair(:note_activity, user: user) + activity = insert(:note_activity, user: user2) + + ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses") + + response = json_response(ret_conn, 200) + + assert length(response) == 2 + + ret_conn = get(conn, "/api/pleroma/admin/instances/test.com/statuses") + + response = json_response(ret_conn, 200) + + assert length(response) == 1 + + ret_conn = get(conn, "/api/pleroma/admin/instances/nonexistent.com/statuses") + + response = json_response(ret_conn, 200) + + assert Enum.empty?(response) + + CommonAPI.repeat(activity.id, user) + + ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses") + response = json_response(ret_conn, 200) + assert length(response) == 2 + + ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses?with_reblogs=true") + response = json_response(ret_conn, 200) + assert length(response) == 3 + end + end + + describe "PATCH /confirm_email" do + test "it confirms emails of two users", %{conn: conn, admin: admin} do + [first_user, second_user] = insert_pair(:user, confirmation_pending: true) + + assert first_user.confirmation_pending == true + assert second_user.confirmation_pending == true + + ret_conn = + patch(conn, "/api/pleroma/admin/users/confirm_email", %{ + nicknames: [ + first_user.nickname, + second_user.nickname + ] + }) + + assert ret_conn.status == 200 + + assert first_user.confirmation_pending == true + assert second_user.confirmation_pending == true + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} confirmed email for users: @#{first_user.nickname}, @#{ + second_user.nickname + }" + end + end + + describe "PATCH /resend_confirmation_email" do + test "it resend emails for two users", %{conn: conn, admin: admin} do + [first_user, second_user] = insert_pair(:user, confirmation_pending: true) + + ret_conn = + patch(conn, "/api/pleroma/admin/users/resend_confirmation_email", %{ + nicknames: [ + first_user.nickname, + second_user.nickname + ] + }) + + assert ret_conn.status == 200 + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} re-sent confirmation email for users: @#{first_user.nickname}, @#{ + second_user.nickname + }" + end + end + + describe "POST /reports/:id/notes" do + setup %{conn: conn, admin: admin} do + [reporter, target_user] = insert_pair(:user) + activity = insert(:note_activity, user: target_user) + + {:ok, %{id: report_id}} = + CommonAPI.report(reporter, %{ + "account_id" => target_user.id, + "comment" => "I feel offended", + "status_ids" => [activity.id] + }) + + post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{ + content: "this is disgusting!" + }) + + post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{ + content: "this is disgusting2!" + }) + + %{ + admin_id: admin.id, + report_id: report_id + } + end + + test "it creates report note", %{admin_id: admin_id, report_id: report_id} do + [note, _] = Repo.all(ReportNote) + + assert %{ + activity_id: ^report_id, + content: "this is disgusting!", + user_id: ^admin_id + } = note + end + + test "it returns reports with notes", %{conn: conn, admin: admin} do + conn = get(conn, "/api/pleroma/admin/reports") + + response = json_response(conn, 200) + notes = hd(response["reports"])["notes"] + [note, _] = notes + + assert note["user"]["nickname"] == admin.nickname + assert note["content"] == "this is disgusting!" + assert note["created_at"] + assert response["total"] == 1 + end + + test "it deletes the note", %{conn: conn, report_id: report_id} do + assert ReportNote |> Repo.all() |> length() == 2 + + [note, _] = Repo.all(ReportNote) + + delete(conn, "/api/pleroma/admin/reports/#{report_id}/notes/#{note.id}") + + assert ReportNote |> Repo.all() |> length() == 1 + end + end + + test "GET /api/pleroma/admin/config/descriptions", %{conn: conn} do + admin = insert(:user, is_admin: true) + + conn = + assign(conn, :user, admin) + |> get("/api/pleroma/admin/config/descriptions") + + assert [child | _others] = json_response(conn, 200) + + assert child["children"] + assert child["key"] + assert String.starts_with?(child["group"], ":") + assert child["description"] + end + + describe "/api/pleroma/admin/stats" do + test "status visibility count", %{conn: conn} do + admin = insert(:user, is_admin: true) + user = insert(:user) + CommonAPI.post(user, %{"visibility" => "public", "status" => "hey"}) + CommonAPI.post(user, %{"visibility" => "unlisted", "status" => "hey"}) + CommonAPI.post(user, %{"visibility" => "unlisted", "status" => "hey"}) + + response = + conn + |> assign(:user, admin) + |> get("/api/pleroma/admin/stats") + |> json_response(200) + + assert %{"direct" => 0, "private" => 0, "public" => 1, "unlisted" => 2} = + response["status_visibility"] + end end end diff --git a/test/web/admin_api/config_test.exs b/test/web/admin_api/config_test.exs deleted file mode 100644 index 204446b79..000000000 --- a/test/web/admin_api/config_test.exs +++ /dev/null @@ -1,497 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.AdminAPI.ConfigTest do - use Pleroma.DataCase, async: true - import Pleroma.Factory - alias Pleroma.Web.AdminAPI.Config - - test "get_by_key/1" do - config = insert(:config) - insert(:config) - - assert config == Config.get_by_params(%{group: config.group, key: config.key}) - end - - test "create/1" do - {:ok, config} = Config.create(%{group: "pleroma", key: "some_key", value: "some_value"}) - assert config == Config.get_by_params(%{group: "pleroma", key: "some_key"}) - end - - test "update/1" do - config = insert(:config) - {:ok, updated} = Config.update(config, %{value: "some_value"}) - loaded = Config.get_by_params(%{group: config.group, key: config.key}) - assert loaded == updated - end - - test "update_or_create/1" do - config = insert(:config) - key2 = "another_key" - - params = [ - %{group: "pleroma", key: key2, value: "another_value"}, - %{group: config.group, key: config.key, value: "new_value"} - ] - - assert Repo.all(Config) |> length() == 1 - - Enum.each(params, &Config.update_or_create(&1)) - - assert Repo.all(Config) |> length() == 2 - - config1 = Config.get_by_params(%{group: config.group, key: config.key}) - config2 = Config.get_by_params(%{group: "pleroma", key: key2}) - - assert config1.value == Config.transform("new_value") - assert config2.value == Config.transform("another_value") - end - - test "delete/1" do - config = insert(:config) - {:ok, _} = Config.delete(%{key: config.key, group: config.group}) - refute Config.get_by_params(%{key: config.key, group: config.group}) - end - - describe "transform/1" do - test "string" do - binary = Config.transform("value as string") - assert binary == :erlang.term_to_binary("value as string") - assert Config.from_binary(binary) == "value as string" - end - - test "boolean" do - binary = Config.transform(false) - assert binary == :erlang.term_to_binary(false) - assert Config.from_binary(binary) == false - end - - test "nil" do - binary = Config.transform(nil) - assert binary == :erlang.term_to_binary(nil) - assert Config.from_binary(binary) == nil - end - - test "integer" do - binary = Config.transform(150) - assert binary == :erlang.term_to_binary(150) - assert Config.from_binary(binary) == 150 - end - - test "atom" do - binary = Config.transform(":atom") - assert binary == :erlang.term_to_binary(:atom) - assert Config.from_binary(binary) == :atom - end - - test "pleroma module" do - binary = Config.transform("Pleroma.Bookmark") - assert binary == :erlang.term_to_binary(Pleroma.Bookmark) - assert Config.from_binary(binary) == Pleroma.Bookmark - end - - test "phoenix module" do - binary = Config.transform("Phoenix.Socket.V1.JSONSerializer") - assert binary == :erlang.term_to_binary(Phoenix.Socket.V1.JSONSerializer) - assert Config.from_binary(binary) == Phoenix.Socket.V1.JSONSerializer - end - - test "sigil" do - binary = Config.transform("~r/comp[lL][aA][iI][nN]er/") - assert binary == :erlang.term_to_binary(~r/comp[lL][aA][iI][nN]er/) - assert Config.from_binary(binary) == ~r/comp[lL][aA][iI][nN]er/ - end - - test "link sigil" do - binary = Config.transform("~r/https:\/\/example.com/") - assert binary == :erlang.term_to_binary(~r/https:\/\/example.com/) - assert Config.from_binary(binary) == ~r/https:\/\/example.com/ - end - - test "link sigil with u modifier" do - binary = Config.transform("~r/https:\/\/example.com/u") - assert binary == :erlang.term_to_binary(~r/https:\/\/example.com/u) - assert Config.from_binary(binary) == ~r/https:\/\/example.com/u - end - - test "link sigil with i modifier" do - binary = Config.transform("~r/https:\/\/example.com/i") - assert binary == :erlang.term_to_binary(~r/https:\/\/example.com/i) - assert Config.from_binary(binary) == ~r/https:\/\/example.com/i - end - - test "link sigil with s modifier" do - binary = Config.transform("~r/https:\/\/example.com/s") - assert binary == :erlang.term_to_binary(~r/https:\/\/example.com/s) - assert Config.from_binary(binary) == ~r/https:\/\/example.com/s - end - - test "2 child tuple" do - binary = Config.transform(%{"tuple" => ["v1", ":v2"]}) - assert binary == :erlang.term_to_binary({"v1", :v2}) - assert Config.from_binary(binary) == {"v1", :v2} - end - - test "tuple with n childs" do - binary = - Config.transform(%{ - "tuple" => [ - "v1", - ":v2", - "Pleroma.Bookmark", - 150, - false, - "Phoenix.Socket.V1.JSONSerializer" - ] - }) - - assert binary == - :erlang.term_to_binary( - {"v1", :v2, Pleroma.Bookmark, 150, false, Phoenix.Socket.V1.JSONSerializer} - ) - - assert Config.from_binary(binary) == - {"v1", :v2, Pleroma.Bookmark, 150, false, Phoenix.Socket.V1.JSONSerializer} - end - - test "tuple with dispatch key" do - binary = Config.transform(%{"tuple" => [":dispatch", ["{:_, - [ - {\"/api/v1/streaming\", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, - {\"/websocket\", Phoenix.Endpoint.CowboyWebSocket, - {Phoenix.Transports.WebSocket, - {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: \"/websocket\"]}}}, - {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}} - ]}"]]}) - - assert binary == - :erlang.term_to_binary( - {:dispatch, - [ - {:_, - [ - {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, - {"/websocket", Phoenix.Endpoint.CowboyWebSocket, - {Phoenix.Transports.WebSocket, - {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: "/websocket"]}}}, - {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}} - ]} - ]} - ) - - assert Config.from_binary(binary) == - {:dispatch, - [ - {:_, - [ - {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, - {"/websocket", Phoenix.Endpoint.CowboyWebSocket, - {Phoenix.Transports.WebSocket, - {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: "/websocket"]}}}, - {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}} - ]} - ]} - end - - test "map with string key" do - binary = Config.transform(%{"key" => "value"}) - assert binary == :erlang.term_to_binary(%{"key" => "value"}) - assert Config.from_binary(binary) == %{"key" => "value"} - end - - test "map with atom key" do - binary = Config.transform(%{":key" => "value"}) - assert binary == :erlang.term_to_binary(%{key: "value"}) - assert Config.from_binary(binary) == %{key: "value"} - end - - test "list of strings" do - binary = Config.transform(["v1", "v2", "v3"]) - assert binary == :erlang.term_to_binary(["v1", "v2", "v3"]) - assert Config.from_binary(binary) == ["v1", "v2", "v3"] - end - - test "list of modules" do - binary = Config.transform(["Pleroma.Repo", "Pleroma.Activity"]) - assert binary == :erlang.term_to_binary([Pleroma.Repo, Pleroma.Activity]) - assert Config.from_binary(binary) == [Pleroma.Repo, Pleroma.Activity] - end - - test "list of atoms" do - binary = Config.transform([":v1", ":v2", ":v3"]) - assert binary == :erlang.term_to_binary([:v1, :v2, :v3]) - assert Config.from_binary(binary) == [:v1, :v2, :v3] - end - - test "list of mixed values" do - binary = - Config.transform([ - "v1", - ":v2", - "Pleroma.Repo", - "Phoenix.Socket.V1.JSONSerializer", - 15, - false - ]) - - assert binary == - :erlang.term_to_binary([ - "v1", - :v2, - Pleroma.Repo, - Phoenix.Socket.V1.JSONSerializer, - 15, - false - ]) - - assert Config.from_binary(binary) == [ - "v1", - :v2, - Pleroma.Repo, - Phoenix.Socket.V1.JSONSerializer, - 15, - false - ] - end - - test "simple keyword" do - binary = Config.transform([%{"tuple" => [":key", "value"]}]) - assert binary == :erlang.term_to_binary([{:key, "value"}]) - assert Config.from_binary(binary) == [{:key, "value"}] - assert Config.from_binary(binary) == [key: "value"] - end - - test "keyword with partial_chain key" do - binary = - Config.transform([%{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}]) - - assert binary == :erlang.term_to_binary(partial_chain: &:hackney_connect.partial_chain/1) - assert Config.from_binary(binary) == [partial_chain: &:hackney_connect.partial_chain/1] - end - - test "keyword" do - binary = - Config.transform([ - %{"tuple" => [":types", "Pleroma.PostgresTypes"]}, - %{"tuple" => [":telemetry_event", ["Pleroma.Repo.Instrumenter"]]}, - %{"tuple" => [":migration_lock", nil]}, - %{"tuple" => [":key1", 150]}, - %{"tuple" => [":key2", "string"]} - ]) - - assert binary == - :erlang.term_to_binary( - types: Pleroma.PostgresTypes, - telemetry_event: [Pleroma.Repo.Instrumenter], - migration_lock: nil, - key1: 150, - key2: "string" - ) - - assert Config.from_binary(binary) == [ - types: Pleroma.PostgresTypes, - telemetry_event: [Pleroma.Repo.Instrumenter], - migration_lock: nil, - key1: 150, - key2: "string" - ] - end - - test "complex keyword with nested mixed childs" do - binary = - Config.transform([ - %{"tuple" => [":uploader", "Pleroma.Uploaders.Local"]}, - %{"tuple" => [":filters", ["Pleroma.Upload.Filter.Dedupe"]]}, - %{"tuple" => [":link_name", true]}, - %{"tuple" => [":proxy_remote", false]}, - %{"tuple" => [":common_map", %{":key" => "value"}]}, - %{ - "tuple" => [ - ":proxy_opts", - [ - %{"tuple" => [":redirect_on_failure", false]}, - %{"tuple" => [":max_body_length", 1_048_576]}, - %{ - "tuple" => [ - ":http", - [%{"tuple" => [":follow_redirect", true]}, %{"tuple" => [":pool", ":upload"]}] - ] - } - ] - ] - } - ]) - - assert binary == - :erlang.term_to_binary( - uploader: Pleroma.Uploaders.Local, - filters: [Pleroma.Upload.Filter.Dedupe], - link_name: true, - proxy_remote: false, - common_map: %{key: "value"}, - proxy_opts: [ - redirect_on_failure: false, - max_body_length: 1_048_576, - http: [ - follow_redirect: true, - pool: :upload - ] - ] - ) - - assert Config.from_binary(binary) == - [ - uploader: Pleroma.Uploaders.Local, - filters: [Pleroma.Upload.Filter.Dedupe], - link_name: true, - proxy_remote: false, - common_map: %{key: "value"}, - proxy_opts: [ - redirect_on_failure: false, - max_body_length: 1_048_576, - http: [ - follow_redirect: true, - pool: :upload - ] - ] - ] - end - - test "common keyword" do - binary = - Config.transform([ - %{"tuple" => [":level", ":warn"]}, - %{"tuple" => [":meta", [":all"]]}, - %{"tuple" => [":path", ""]}, - %{"tuple" => [":val", nil]}, - %{"tuple" => [":webhook_url", "https://hooks.slack.com/services/YOUR-KEY-HERE"]} - ]) - - assert binary == - :erlang.term_to_binary( - level: :warn, - meta: [:all], - path: "", - val: nil, - webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE" - ) - - assert Config.from_binary(binary) == [ - level: :warn, - meta: [:all], - path: "", - val: nil, - webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE" - ] - end - - test "complex keyword with sigil" do - binary = - Config.transform([ - %{"tuple" => [":federated_timeline_removal", []]}, - %{"tuple" => [":reject", ["~r/comp[lL][aA][iI][nN]er/"]]}, - %{"tuple" => [":replace", []]} - ]) - - assert binary == - :erlang.term_to_binary( - federated_timeline_removal: [], - reject: [~r/comp[lL][aA][iI][nN]er/], - replace: [] - ) - - assert Config.from_binary(binary) == - [federated_timeline_removal: [], reject: [~r/comp[lL][aA][iI][nN]er/], replace: []] - end - - test "complex keyword with tuples with more than 2 values" do - binary = - Config.transform([ - %{ - "tuple" => [ - ":http", - [ - %{ - "tuple" => [ - ":key1", - [ - %{ - "tuple" => [ - ":_", - [ - %{ - "tuple" => [ - "/api/v1/streaming", - "Pleroma.Web.MastodonAPI.WebsocketHandler", - [] - ] - }, - %{ - "tuple" => [ - "/websocket", - "Phoenix.Endpoint.CowboyWebSocket", - %{ - "tuple" => [ - "Phoenix.Transports.WebSocket", - %{ - "tuple" => [ - "Pleroma.Web.Endpoint", - "Pleroma.Web.UserSocket", - [] - ] - } - ] - } - ] - }, - %{ - "tuple" => [ - ":_", - "Phoenix.Endpoint.Cowboy2Handler", - %{"tuple" => ["Pleroma.Web.Endpoint", []]} - ] - } - ] - ] - } - ] - ] - } - ] - ] - } - ]) - - assert binary == - :erlang.term_to_binary( - http: [ - key1: [ - _: [ - {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, - {"/websocket", Phoenix.Endpoint.CowboyWebSocket, - {Phoenix.Transports.WebSocket, - {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, []}}}, - {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}} - ] - ] - ] - ) - - assert Config.from_binary(binary) == [ - http: [ - key1: [ - {:_, - [ - {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, - {"/websocket", Phoenix.Endpoint.CowboyWebSocket, - {Phoenix.Transports.WebSocket, - {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, []}}}, - {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}} - ]} - ] - ] - ] - end - end -end diff --git a/test/web/admin_api/search_test.exs b/test/web/admin_api/search_test.exs index 501a8d007..e0e3d4153 100644 --- a/test/web/admin_api/search_test.exs +++ b/test/web/admin_api/search_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.AdminAPI.SearchTest do @@ -47,9 +47,9 @@ defmodule Pleroma.Web.AdminAPI.SearchTest do end test "it returns active/deactivated users" do - insert(:user, info: %{deactivated: true}) - insert(:user, info: %{deactivated: true}) - insert(:user, info: %{deactivated: false}) + insert(:user, deactivated: true) + insert(:user, deactivated: true) + insert(:user, deactivated: false) {:ok, _results, active_count} = Search.user(%{ @@ -70,7 +70,7 @@ defmodule Pleroma.Web.AdminAPI.SearchTest do test "it returns specific user" do insert(:user) insert(:user) - user = insert(:user, nickname: "bob", local: true, info: %{deactivated: false}) + user = insert(:user, nickname: "bob", local: true, deactivated: false) {:ok, _results, total_count} = Search.user(%{query: ""}) @@ -108,7 +108,7 @@ defmodule Pleroma.Web.AdminAPI.SearchTest do end test "it returns admin user" do - admin = insert(:user, info: %{is_admin: true}) + admin = insert(:user, is_admin: true) insert(:user) insert(:user) @@ -119,7 +119,7 @@ defmodule Pleroma.Web.AdminAPI.SearchTest do end test "it returns moderator user" do - moderator = insert(:user, info: %{is_moderator: true}) + moderator = insert(:user, is_moderator: true) insert(:user) insert(:user) diff --git a/test/web/admin_api/views/report_view_test.exs b/test/web/admin_api/views/report_view_test.exs index a00c9c579..5db6629f2 100644 --- a/test/web/admin_api/views/report_view_test.exs +++ b/test/web/admin_api/views/report_view_test.exs @@ -1,10 +1,11 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.AdminAPI.ReportViewTest do use Pleroma.DataCase import Pleroma.Factory + alias Pleroma.Web.AdminAPI.Report alias Pleroma.Web.AdminAPI.ReportView alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.AccountView @@ -20,21 +21,22 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do content: nil, actor: Map.merge( - AccountView.render("account.json", %{user: user}), + AccountView.render("show.json", %{user: user}), Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user}) ), account: Map.merge( - AccountView.render("account.json", %{user: other_user}), + AccountView.render("show.json", %{user: other_user}), Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: other_user}) ), statuses: [], + notes: [], state: "open", id: activity.id } result = - ReportView.render("show.json", %{report: activity}) + ReportView.render("show.json", Report.extract_report_info(activity)) |> Map.delete(:created_at) assert result == expected @@ -48,25 +50,28 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do {:ok, report_activity} = CommonAPI.report(user, %{"account_id" => other_user.id, "status_ids" => [activity.id]}) + other_user = Pleroma.User.get_by_id(other_user.id) + expected = %{ content: nil, actor: Map.merge( - AccountView.render("account.json", %{user: user}), + AccountView.render("show.json", %{user: user}), Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user}) ), account: Map.merge( - AccountView.render("account.json", %{user: other_user}), + AccountView.render("show.json", %{user: other_user}), Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: other_user}) ), - statuses: [StatusView.render("status.json", %{activity: activity})], + statuses: [StatusView.render("show.json", %{activity: activity})], state: "open", + notes: [], id: report_activity.id } result = - ReportView.render("show.json", %{report: report_activity}) + ReportView.render("show.json", Report.extract_report_info(report_activity)) |> Map.delete(:created_at) assert result == expected @@ -78,7 +83,9 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do {:ok, activity} = CommonAPI.report(user, %{"account_id" => other_user.id}) {:ok, activity} = CommonAPI.update_report_state(activity.id, "closed") - assert %{state: "closed"} = ReportView.render("show.json", %{report: activity}) + + assert %{state: "closed"} = + ReportView.render("show.json", Report.extract_report_info(activity)) end test "renders report description" do @@ -92,7 +99,7 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do }) assert %{content: "posts are too good for this instance"} = - ReportView.render("show.json", %{report: activity}) + ReportView.render("show.json", Report.extract_report_info(activity)) end test "sanitizes report description" do @@ -109,7 +116,7 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do activity = Map.put(activity, :data, data) refute "<script> alert('hecked :D:D:D:D:D:D:D') </script>" == - ReportView.render("show.json", %{report: activity})[:content] + ReportView.render("show.json", Report.extract_report_info(activity))[:content] end test "doesn't error out when the user doesn't exists" do @@ -125,6 +132,6 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do Pleroma.User.delete(other_user) Pleroma.User.invalidate_cache(other_user) - assert %{} = ReportView.render("show.json", %{report: activity}) + assert %{} = ReportView.render("show.json", Report.extract_report_info(activity)) end end diff --git a/test/web/auth/authenticator_test.exs b/test/web/auth/authenticator_test.exs index fea5c8209..d54253343 100644 --- a/test/web/auth/authenticator_test.exs +++ b/test/web/auth/authenticator_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Auth.AuthenticatorTest do diff --git a/test/web/chat_channel_test.exs b/test/web/chat_channel_test.exs new file mode 100644 index 000000000..68c24a9f9 --- /dev/null +++ b/test/web/chat_channel_test.exs @@ -0,0 +1,37 @@ +defmodule Pleroma.Web.ChatChannelTest do + use Pleroma.Web.ChannelCase + alias Pleroma.Web.ChatChannel + alias Pleroma.Web.UserSocket + + import Pleroma.Factory + + setup do + user = insert(:user) + + {:ok, _, socket} = + socket(UserSocket, "", %{user_name: user.nickname}) + |> subscribe_and_join(ChatChannel, "chat:public") + + {:ok, socket: socket} + end + + test "it broadcasts a message", %{socket: socket} do + push(socket, "new_msg", %{"text" => "why is tenshi eating a corndog so cute?"}) + assert_broadcast("new_msg", %{text: "why is tenshi eating a corndog so cute?"}) + end + + describe "message lengths" do + clear_config([:instance, :chat_limit]) + + test "it ignores messages of length zero", %{socket: socket} do + push(socket, "new_msg", %{"text" => ""}) + refute_broadcast("new_msg", %{text: ""}) + end + + test "it ignores messages above a certain length", %{socket: socket} do + Pleroma.Config.put([:instance, :chat_limit], 2) + push(socket, "new_msg", %{"text" => "123"}) + refute_broadcast("new_msg", %{text: "123"}) + end + end +end diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 693dd40bd..299d968db 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.CommonAPITest do @@ -10,6 +10,7 @@ defmodule Pleroma.Web.CommonAPITest do alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.AdminAPI.AccountView alias Pleroma.Web.CommonAPI import Pleroma.Factory @@ -67,6 +68,7 @@ defmodule Pleroma.Web.CommonAPITest do har = insert(:user) jafnhar = insert(:user) tridi = insert(:user) + Pleroma.Config.put([:instance, :safe_dm_mentions], true) {:ok, activity} = @@ -100,7 +102,7 @@ defmodule Pleroma.Web.CommonAPITest do {:ok, activity} = CommonAPI.update(user) user = User.get_cached_by_ap_id(user.ap_id) - [firefox] = user.info.source_data["tag"] + [firefox] = user.source_data["tag"] assert firefox["name"] == ":firefox:" @@ -140,7 +142,7 @@ defmodule Pleroma.Web.CommonAPITest do object = Object.normalize(activity) - assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')" + assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')" end test "it filters out obviously bad tags when accepting a post as Markdown" do @@ -156,7 +158,7 @@ defmodule Pleroma.Web.CommonAPITest do object = Object.normalize(activity) - assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')" + assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')" end test "it does not allow replies to direct messages that are not direct messages themselves" do @@ -226,6 +228,35 @@ defmodule Pleroma.Web.CommonAPITest do end describe "reactions" do + test "reacting to a status with an emoji" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) + + {:ok, reaction, _} = CommonAPI.react_with_emoji(activity.id, user, "👍") + + assert reaction.data["actor"] == user.ap_id + assert reaction.data["content"] == "👍" + + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) + + {:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".") + end + + test "unreacting to a status with an emoji" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) + {:ok, reaction, _} = CommonAPI.react_with_emoji(activity.id, user, "👍") + + {:ok, unreaction, _} = CommonAPI.unreact_with_emoji(activity.id, user, "👍") + + assert unreaction.data["type"] == "Undo" + assert unreaction.data["object"] == reaction.data["id"] + end + test "repeating a status" do user = insert(:user) other_user = insert(:user) @@ -235,6 +266,18 @@ defmodule Pleroma.Web.CommonAPITest do {:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, user) end + test "repeating a status privately" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) + + {:ok, %Activity{} = announce_activity, _} = + CommonAPI.repeat(activity.id, user, %{"visibility" => "private"}) + + assert Visibility.is_private?(announce_activity) + end + test "favoriting a status" do user = insert(:user) other_user = insert(:user) @@ -244,22 +287,22 @@ defmodule Pleroma.Web.CommonAPITest do {:ok, %Activity{}, _} = CommonAPI.favorite(activity.id, user) end - test "retweeting a status twice returns an error" do + test "retweeting a status twice returns the status" do user = insert(:user) other_user = insert(:user) {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) - {:ok, %Activity{}, _object} = CommonAPI.repeat(activity.id, user) - {:error, _} = CommonAPI.repeat(activity.id, user) + {:ok, %Activity{} = activity, object} = CommonAPI.repeat(activity.id, user) + {:ok, ^activity, ^object} = CommonAPI.repeat(activity.id, user) end - test "favoriting a status twice returns an error" do + test "favoriting a status twice returns the status" do user = insert(:user) other_user = insert(:user) {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) - {:ok, %Activity{}, _object} = CommonAPI.favorite(activity.id, user) - {:error, _} = CommonAPI.favorite(activity.id, user) + {:ok, %Activity{} = activity, object} = CommonAPI.favorite(activity.id, user) + {:ok, ^activity, ^object} = CommonAPI.favorite(activity.id, user) end end @@ -279,7 +322,22 @@ defmodule Pleroma.Web.CommonAPITest do id = activity.id user = refresh_record(user) - assert %User{info: %{pinned_activities: [^id]}} = user + assert %User{pinned_activities: [^id]} = user + end + + test "pin poll", %{user: user} do + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => "How is fediverse today?", + "poll" => %{"options" => ["Absolutely outstanding", "Not good"], "expires_in" => 20} + }) + + assert {:ok, ^activity} = CommonAPI.pin(activity.id, user) + + id = activity.id + user = refresh_record(user) + + assert %User{pinned_activities: [^id]} = user end test "unlisted statuses can be pinned", %{user: user} do @@ -313,7 +371,7 @@ defmodule Pleroma.Web.CommonAPITest do user = refresh_record(user) - assert %User{info: %{pinned_activities: []}} = user + assert %User{pinned_activities: []} = user end test "should unpin when deleting a status", %{user: user, activity: activity} do @@ -325,7 +383,7 @@ defmodule Pleroma.Web.CommonAPITest do user = refresh_record(user) - assert %User{info: %{pinned_activities: []}} = user + assert %User{pinned_activities: []} = user end end @@ -373,6 +431,14 @@ defmodule Pleroma.Web.CommonAPITest do "status_ids" => [activity.id] } + note_obj = %{ + "type" => "Note", + "id" => activity_ap_id, + "content" => "foobar", + "published" => activity.object.data["published"], + "actor" => AccountView.render("show.json", %{user: target_user}) + } + assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data) assert %Activity{ @@ -380,7 +446,7 @@ defmodule Pleroma.Web.CommonAPITest do data: %{ "type" => "Flag", "content" => ^comment, - "object" => [^target_ap_id, ^activity_ap_id], + "object" => [^target_ap_id, ^note_obj], "state" => "open" } } = flag_activity @@ -400,6 +466,11 @@ defmodule Pleroma.Web.CommonAPITest do {:ok, report} = CommonAPI.update_report_state(report_id, "resolved") assert report.data["state"] == "resolved" + + [reported_user, activity_id] = report.data["object"] + + assert reported_user == target_user.ap_id + assert activity_id == activity.data["id"] end test "does not update report state when state is unsupported" do @@ -415,6 +486,35 @@ defmodule Pleroma.Web.CommonAPITest do assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"} end + + test "updates state of multiple reports" do + [reporter, target_user] = insert_pair(:user) + activity = insert(:note_activity, user: target_user) + + {:ok, %Activity{id: first_report_id}} = + CommonAPI.report(reporter, %{ + "account_id" => target_user.id, + "comment" => "I feel offended", + "status_ids" => [activity.id] + }) + + {:ok, %Activity{id: second_report_id}} = + CommonAPI.report(reporter, %{ + "account_id" => target_user.id, + "comment" => "I feel very offended!", + "status_ids" => [activity.id] + }) + + {:ok, report_ids} = + CommonAPI.update_report_state([first_report_id, second_report_id], "resolved") + + first_report = Activity.get_by_id(first_report_id) + second_report = Activity.get_by_id(second_report_id) + + assert report_ids -- [first_report_id, second_report_id] == [] + assert first_report.data["state"] == "resolved" + assert second_report.data["state"] == "resolved" + end end describe "reblog muting" do @@ -427,14 +527,14 @@ defmodule Pleroma.Web.CommonAPITest do end test "add a reblog mute", %{muter: muter, muted: muted} do - {:ok, muter} = CommonAPI.hide_reblogs(muter, muted) + {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted) assert User.showing_reblogs?(muter, muted) == false end test "remove a reblog mute", %{muter: muter, muted: muted} do - {:ok, muter} = CommonAPI.hide_reblogs(muter, muted) - {:ok, muter} = CommonAPI.show_reblogs(muter, muted) + {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted) + {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted) assert User.showing_reblogs?(muter, muted) == true end @@ -444,7 +544,7 @@ defmodule Pleroma.Web.CommonAPITest do test "also unsubscribes a user" do [follower, followed] = insert_pair(:user) {:ok, follower, followed, _} = CommonAPI.follow(follower, followed) - {:ok, followed} = User.subscribe(follower, followed) + {:ok, _subscription} = User.subscribe(follower, followed) assert User.subscribed_to?(follower, followed) @@ -452,11 +552,55 @@ defmodule Pleroma.Web.CommonAPITest do refute User.subscribed_to?(follower, followed) end + + test "cancels a pending follow for a local user" do + follower = insert(:user) + followed = insert(:user, locked: true) + + assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} = + CommonAPI.follow(follower, followed) + + assert User.get_follow_state(follower, followed) == "pending" + assert {:ok, follower} = CommonAPI.unfollow(follower, followed) + assert User.get_follow_state(follower, followed) == nil + + assert %{id: ^activity_id, data: %{"state" => "cancelled"}} = + Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed) + + assert %{ + data: %{ + "type" => "Undo", + "object" => %{"type" => "Follow", "state" => "cancelled"} + } + } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower) + end + + test "cancels a pending follow for a remote user" do + follower = insert(:user) + followed = insert(:user, locked: true, local: false, ap_enabled: true) + + assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} = + CommonAPI.follow(follower, followed) + + assert User.get_follow_state(follower, followed) == "pending" + assert {:ok, follower} = CommonAPI.unfollow(follower, followed) + assert User.get_follow_state(follower, followed) == nil + + assert %{id: ^activity_id, data: %{"state" => "cancelled"}} = + Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed) + + assert %{ + data: %{ + "type" => "Undo", + "object" => %{"type" => "Follow", "state" => "cancelled"} + } + } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower) + end end describe "accept_follow_request/2" do test "after acceptance, it sets all existing pending follow request states to 'accept'" do - user = insert(:user, info: %{locked: true}) + user = insert(:user, locked: true) follower = insert(:user) follower_two = insert(:user) @@ -476,7 +620,7 @@ defmodule Pleroma.Web.CommonAPITest do end test "after rejection, it sets all existing pending follow request states to 'reject'" do - user = insert(:user, info: %{locked: true}) + user = insert(:user, locked: true) follower = insert(:user) follower_two = insert(:user) @@ -514,4 +658,43 @@ defmodule Pleroma.Web.CommonAPITest do assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1]) end end + + describe "listen/2" do + test "returns a valid activity" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.listen(user, %{ + "title" => "lain radio episode 1", + "album" => "lain radio", + "artist" => "lain", + "length" => 180_000 + }) + + object = Object.normalize(activity) + + assert object.data["title"] == "lain radio episode 1" + + assert Visibility.get_visibility(activity) == "public" + end + + test "respects visibility=private" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.listen(user, %{ + "title" => "lain radio episode 1", + "album" => "lain radio", + "artist" => "lain", + "length" => 180_000, + "visibility" => "private" + }) + + object = Object.normalize(activity) + + assert object.data["title"] == "lain radio episode 1" + + assert Visibility.get_visibility(activity) == "private" + end + end end diff --git a/test/web/common_api/common_api_utils_test.exs b/test/web/common_api/common_api_utils_test.exs index c281dd1f1..b380d10d8 100644 --- a/test/web/common_api/common_api_utils_test.exs +++ b/test/web/common_api/common_api_utils_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.CommonAPI.UtilsTest do @@ -157,11 +157,11 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do text = "**hello world**\n\n*another @user__test and @user__test google.com paragraph*" expected = - "<p><strong>hello world</strong></p>\n<p><em>another <span class=\"h-card\"><a data-user=\"#{ + ~s(<p><strong>hello world</strong></p>\n<p><em>another <span class="h-card"><a data-user="#{ user.id - }\" class=\"u-url mention\" href=\"http://foo.com/user__test\">@<span>user__test</span></a></span> and <span class=\"h-card\"><a data-user=\"#{ + }" class="u-url mention" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> and <span class="h-card"><a data-user="#{ user.id - }\" class=\"u-url mention\" href=\"http://foo.com/user__test\">@<span>user__test</span></a></span> <a href=\"http://google.com\">google.com</a> paragraph</em></p>\n" + }" class="u-url mention" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> <a href="http://google.com" rel="ugc">google.com</a> paragraph</em></p>\n) {output, _, _} = Utils.format_input(text, "text/markdown") @@ -307,7 +307,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "private", nil) assert length(to) == 2 - assert length(cc) == 0 + assert Enum.empty?(cc) assert mentioned_user.ap_id in to assert user.follower_address in to @@ -323,7 +323,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "private", nil) assert length(to) == 3 - assert length(cc) == 0 + assert Enum.empty?(cc) assert mentioned_user.ap_id in to assert third_user.ap_id in to @@ -338,7 +338,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "direct", nil) assert length(to) == 1 - assert length(cc) == 0 + assert Enum.empty?(cc) assert mentioned_user.ap_id in to end @@ -353,7 +353,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "direct", nil) assert length(to) == 2 - assert length(cc) == 0 + assert Enum.empty?(cc) assert mentioned_user.ap_id in to assert third_user.ap_id in to @@ -575,11 +575,11 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do end describe "maybe_add_attachments/3" do - test "returns parsed results when no_links is true" do + test "returns parsed results when attachment_links is false" do assert Utils.maybe_add_attachments( {"test", [], ["tags"]}, [], - true + false ) == {"test", [], ["tags"]} end @@ -589,7 +589,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do assert Utils.maybe_add_attachments( {"test", [], ["tags"]}, [attachment], - false + true ) == { "test<br><a href=\"SakuraPM.png\" class='attachment'>SakuraPM.png</a>", [], diff --git a/test/web/digest_email_worker_test.exs b/test/web/digest_email_worker_test.exs deleted file mode 100644 index 15002330f..000000000 --- a/test/web/digest_email_worker_test.exs +++ /dev/null @@ -1,31 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.DigestEmailWorkerTest do - use Pleroma.DataCase - import Pleroma.Factory - - alias Pleroma.DigestEmailWorker - alias Pleroma.User - alias Pleroma.Web.CommonAPI - - test "it sends digest emails" do - user = insert(:user) - - date = - Timex.now() - |> Timex.shift(days: -10) - |> Timex.to_naive_datetime() - - user2 = insert(:user, last_digest_emailed_at: date) - User.switch_email_notifications(user2, "digest", true) - CommonAPI.post(user, %{"status" => "hey @#{user2.nickname}!"}) - - DigestEmailWorker.perform() - - assert_received {:email, email} - assert email.to == [{user2.name, user2.email}] - assert email.subject == "Your digest from #{Pleroma.Config.get(:instance)[:name]}" - end -end diff --git a/test/web/fallback_test.exs b/test/web/fallback_test.exs index c13db9526..3919ef93a 100644 --- a/test/web/fallback_test.exs +++ b/test/web/fallback_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.FallbackTest do diff --git a/test/web/federator_test.exs b/test/web/federator_test.exs index 09e54533f..d2ee2267c 100644 --- a/test/web/federator_test.exs +++ b/test/web/federator_test.exs @@ -1,12 +1,17 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.FederatorTest do alias Pleroma.Instances + alias Pleroma.Tests.ObanHelpers alias Pleroma.Web.CommonAPI alias Pleroma.Web.Federator + alias Pleroma.Workers.PublisherWorker + use Pleroma.DataCase + use Oban.Testing, repo: Pleroma.Repo + import Pleroma.Factory import Mock @@ -24,15 +29,6 @@ defmodule Pleroma.Web.FederatorTest do clear_config([:instance, :rewrite_policy]) clear_config([:mrf_keyword]) - describe "Publisher.perform" do - test "call `perform` with unknown task" do - assert { - :error, - "Don't know what to do with this" - } = Pleroma.Web.Federator.Publisher.perform("test", :ok, :ok) - end - end - describe "Publish an activity" do setup do user = insert(:user) @@ -53,6 +49,7 @@ defmodule Pleroma.Web.FederatorTest do } do with_mocks([relay_mock]) do Federator.publish(activity) + ObanHelpers.perform(all_enqueued(worker: PublisherWorker)) end assert_received :relay_publish @@ -66,6 +63,7 @@ defmodule Pleroma.Web.FederatorTest do with_mocks([relay_mock]) do Federator.publish(activity) + ObanHelpers.perform(all_enqueued(worker: PublisherWorker)) end refute_received :relay_publish @@ -73,10 +71,7 @@ defmodule Pleroma.Web.FederatorTest do end describe "Targets reachability filtering in `publish`" do - test_with_mock "it federates only to reachable instances via AP", - Pleroma.Web.ActivityPub.Publisher, - [:passthrough], - [] do + test "it federates only to reachable instances via AP" do user = insert(:user) {inbox1, inbox2} = @@ -86,14 +81,16 @@ defmodule Pleroma.Web.FederatorTest do local: false, nickname: "nick1@domain.com", ap_id: "https://domain.com/users/nick1", - info: %{ap_enabled: true, source_data: %{"inbox" => inbox1}} + source_data: %{"inbox" => inbox1}, + ap_enabled: true }) insert(:user, %{ local: false, nickname: "nick2@domain2.com", ap_id: "https://domain2.com/users/nick2", - info: %{ap_enabled: true, source_data: %{"inbox" => inbox2}} + source_data: %{"inbox" => inbox2}, + ap_enabled: true }) dt = NaiveDateTime.utc_now() @@ -104,92 +101,17 @@ defmodule Pleroma.Web.FederatorTest do {:ok, _activity} = CommonAPI.post(user, %{"status" => "HI @nick1@domain.com, @nick2@domain2.com!"}) - assert called( - Pleroma.Web.ActivityPub.Publisher.publish_one(%{ - inbox: inbox1, - unreachable_since: dt - }) - ) - - refute called(Pleroma.Web.ActivityPub.Publisher.publish_one(%{inbox: inbox2})) - end - - test_with_mock "it federates only to reachable instances via Websub", - Pleroma.Web.Websub, - [:passthrough], - [] do - user = insert(:user) - websub_topic = Pleroma.Web.OStatus.feed_path(user) - - sub1 = - insert(:websub_subscription, %{ - topic: websub_topic, - state: "active", - callback: "http://pleroma.soykaf.com/cb" - }) - - sub2 = - insert(:websub_subscription, %{ - topic: websub_topic, - state: "active", - callback: "https://pleroma2.soykaf.com/cb" - }) + expected_dt = NaiveDateTime.to_iso8601(dt) - dt = NaiveDateTime.utc_now() - Instances.set_unreachable(sub2.callback, dt) + ObanHelpers.perform(all_enqueued(worker: PublisherWorker)) - Instances.set_consistently_unreachable(sub1.callback) - - {:ok, _activity} = CommonAPI.post(user, %{"status" => "HI"}) - - assert called( - Pleroma.Web.Websub.publish_one(%{ - callback: sub2.callback, - unreachable_since: dt - }) + assert ObanHelpers.member?( + %{ + "op" => "publish_one", + "params" => %{"inbox" => inbox1, "unreachable_since" => expected_dt} + }, + all_enqueued(worker: PublisherWorker) ) - - refute called(Pleroma.Web.Websub.publish_one(%{callback: sub1.callback})) - end - - test_with_mock "it federates only to reachable instances via Salmon", - Pleroma.Web.Salmon, - [:passthrough], - [] do - user = insert(:user) - - remote_user1 = - insert(:user, %{ - local: false, - nickname: "nick1@domain.com", - ap_id: "https://domain.com/users/nick1", - info: %{salmon: "https://domain.com/salmon"} - }) - - remote_user2 = - insert(:user, %{ - local: false, - nickname: "nick2@domain2.com", - ap_id: "https://domain2.com/users/nick2", - info: %{salmon: "https://domain2.com/salmon"} - }) - - dt = NaiveDateTime.utc_now() - Instances.set_unreachable(remote_user2.ap_id, dt) - - Instances.set_consistently_unreachable("domain.com") - - {:ok, _activity} = - CommonAPI.post(user, %{"status" => "HI @nick1@domain.com, @nick2@domain2.com!"}) - - assert called( - Pleroma.Web.Salmon.publish_one(%{ - recipient: remote_user2, - unreachable_since: dt - }) - ) - - refute called(Pleroma.Web.Salmon.publish_one(%{recipient: remote_user1})) end end @@ -209,7 +131,8 @@ defmodule Pleroma.Web.FederatorTest do "to" => ["https://www.w3.org/ns/activitystreams#Public"] } - {:ok, _activity} = Federator.incoming_ap_doc(params) + assert {:ok, job} = Federator.incoming_ap_doc(params) + assert {:ok, _activity} = ObanHelpers.perform(job) end test "rejects incoming AP docs with incorrect origin" do @@ -227,7 +150,8 @@ defmodule Pleroma.Web.FederatorTest do "to" => ["https://www.w3.org/ns/activitystreams#Public"] } - :error = Federator.incoming_ap_doc(params) + assert {:ok, job} = Federator.incoming_ap_doc(params) + assert :error = ObanHelpers.perform(job) end test "it does not crash if MRF rejects the post" do @@ -242,7 +166,8 @@ defmodule Pleroma.Web.FederatorTest do File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() - assert Federator.incoming_ap_doc(params) == :error + assert {:ok, job} = Federator.incoming_ap_doc(params) + assert :error = ObanHelpers.perform(job) end end end diff --git a/test/web/feed/tag_controller_test.exs b/test/web/feed/tag_controller_test.exs new file mode 100644 index 000000000..5950605e8 --- /dev/null +++ b/test/web/feed/tag_controller_test.exs @@ -0,0 +1,154 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Feed.TagControllerTest do + use Pleroma.Web.ConnCase + + import Pleroma.Factory + import SweetXml + + alias Pleroma.Web.Feed.FeedView + + clear_config([:feed]) + + test "gets a feed (ATOM)", %{conn: conn} do + Pleroma.Config.put( + [:feed, :post_title], + %{max_length: 25, omission: "..."} + ) + + user = insert(:user) + {:ok, activity1} = Pleroma.Web.CommonAPI.post(user, %{"status" => "yeah #PleromaArt"}) + + object = Pleroma.Object.normalize(activity1) + + object_data = + Map.put(object.data, "attachment", [ + %{ + "url" => [ + %{ + "href" => + "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", + "mediaType" => "video/mp4", + "type" => "Link" + } + ] + } + ]) + + object + |> Ecto.Changeset.change(data: object_data) + |> Pleroma.Repo.update() + + {:ok, _activity2} = + Pleroma.Web.CommonAPI.post(user, %{"status" => "42 This is :moominmamma #PleromaArt"}) + + {:ok, _activity3} = Pleroma.Web.CommonAPI.post(user, %{"status" => "This is :moominmamma"}) + + response = + conn + |> put_req_header("content-type", "application/atom+xml") + |> get(tag_feed_path(conn, :feed, "pleromaart.atom")) + |> response(200) + + xml = parse(response) + + assert xpath(xml, ~x"//feed/title/text()") == '#pleromaart' + + assert xpath(xml, ~x"//feed/entry/title/text()"l) == [ + '42 This is :moominmamm...', + 'yeah #PleromaArt' + ] + + assert xpath(xml, ~x"//feed/entry/author/name/text()"ls) == [user.nickname, user.nickname] + assert xpath(xml, ~x"//feed/entry/author/id/text()"ls) == [user.ap_id, user.ap_id] + end + + test "gets a feed (RSS)", %{conn: conn} do + Pleroma.Config.put( + [:feed, :post_title], + %{max_length: 25, omission: "..."} + ) + + user = insert(:user) + {:ok, activity1} = Pleroma.Web.CommonAPI.post(user, %{"status" => "yeah #PleromaArt"}) + + object = Pleroma.Object.normalize(activity1) + + object_data = + Map.put(object.data, "attachment", [ + %{ + "url" => [ + %{ + "href" => + "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", + "mediaType" => "video/mp4", + "type" => "Link" + } + ] + } + ]) + + object + |> Ecto.Changeset.change(data: object_data) + |> Pleroma.Repo.update() + + {:ok, activity2} = + Pleroma.Web.CommonAPI.post(user, %{"status" => "42 This is :moominmamma #PleromaArt"}) + + {:ok, _activity3} = Pleroma.Web.CommonAPI.post(user, %{"status" => "This is :moominmamma"}) + + response = + conn + |> put_req_header("content-type", "application/rss+xml") + |> get(tag_feed_path(conn, :feed, "pleromaart.rss")) + |> response(200) + + xml = parse(response) + assert xpath(xml, ~x"//channel/title/text()") == '#pleromaart' + + assert xpath(xml, ~x"//channel/description/text()"s) == + "These are public toots tagged with #pleromaart. You can interact with them if you have an account anywhere in the fediverse." + + assert xpath(xml, ~x"//channel/link/text()") == + '#{Pleroma.Web.base_url()}/tags/pleromaart.rss' + + assert xpath(xml, ~x"//channel/webfeeds:logo/text()") == + '#{Pleroma.Web.base_url()}/static/logo.png' + + assert xpath(xml, ~x"//channel/item/title/text()"l) == [ + '42 This is :moominmamm...', + 'yeah #PleromaArt' + ] + + assert xpath(xml, ~x"//channel/item/pubDate/text()"sl) == [ + FeedView.pub_date(activity1.data["published"]), + FeedView.pub_date(activity2.data["published"]) + ] + + assert xpath(xml, ~x"//channel/item/enclosure/@url"sl) == [ + "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4" + ] + + obj1 = Pleroma.Object.normalize(activity1) + obj2 = Pleroma.Object.normalize(activity2) + + assert xpath(xml, ~x"//channel/item/description/text()"sl) == [ + HtmlEntities.decode(FeedView.activity_content(obj2)), + HtmlEntities.decode(FeedView.activity_content(obj1)) + ] + + response = + conn + |> put_req_header("content-type", "application/atom+xml") + |> get(tag_feed_path(conn, :feed, "pleromaart")) + |> response(200) + + xml = parse(response) + assert xpath(xml, ~x"//channel/title/text()") == '#pleromaart' + + assert xpath(xml, ~x"//channel/description/text()"s) == + "These are public toots tagged with #pleromaart. You can interact with them if you have an account anywhere in the fediverse." + end +end diff --git a/test/web/feed/user_controller_test.exs b/test/web/feed/user_controller_test.exs new file mode 100644 index 000000000..19a019060 --- /dev/null +++ b/test/web/feed/user_controller_test.exs @@ -0,0 +1,251 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Feed.UserControllerTest do + use Pleroma.Web.ConnCase + + import Pleroma.Factory + import SweetXml + + alias Pleroma.Object + alias Pleroma.User + + clear_config([:feed]) + + test "gets a feed", %{conn: conn} do + Pleroma.Config.put( + [:feed, :post_title], + %{max_length: 10, omission: "..."} + ) + + activity = insert(:note_activity) + + note = + insert(:note, + data: %{ + "content" => "This is :moominmamma: note ", + "attachment" => [ + %{ + "url" => [%{"mediaType" => "image/png", "href" => "https://pleroma.gov/image.png"}] + } + ], + "inReplyTo" => activity.data["id"] + } + ) + + note_activity = insert(:note_activity, note: note) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) + + note2 = + insert(:note, + user: user, + data: %{"content" => "42 This is :moominmamma: note ", "inReplyTo" => activity.data["id"]} + ) + + _note_activity2 = insert(:note_activity, note: note2) + object = Object.normalize(note_activity) + + resp = + conn + |> put_req_header("content-type", "application/atom+xml") + |> get(user_feed_path(conn, :feed, user.nickname)) + |> response(200) + + activity_titles = + resp + |> SweetXml.parse() + |> SweetXml.xpath(~x"//entry/title/text()"l) + + assert activity_titles == ['42 This...', 'This is...'] + assert resp =~ object.data["content"] + end + + test "returns 404 for a missing feed", %{conn: conn} do + conn = + conn + |> put_req_header("content-type", "application/atom+xml") + |> get(user_feed_path(conn, :feed, "nonexisting")) + + assert response(conn, 404) + end + + describe "feed_redirect" do + test "undefined format. it redirects to feed", %{conn: conn} do + note_activity = insert(:note_activity) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) + + response = + conn + |> put_req_header("accept", "application/xml") + |> get("/users/#{user.nickname}") + |> response(302) + + assert response == + "<html><body>You are being <a href=\"#{Pleroma.Web.base_url()}/users/#{ + user.nickname + }/feed.atom\">redirected</a>.</body></html>" + end + + test "undefined format. it returns error when user not found", %{conn: conn} do + response = + conn + |> put_req_header("accept", "application/xml") + |> get(user_feed_path(conn, :feed, "jimm")) + |> response(404) + + assert response == ~S({"error":"Not found"}) + end + + test "activity+json format. it redirects on actual feed of user", %{conn: conn} do + note_activity = insert(:note_activity) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) + + response = + conn + |> put_req_header("accept", "application/activity+json") + |> get("/users/#{user.nickname}") + |> json_response(200) + + assert response["endpoints"] == %{ + "oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize", + "oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps", + "oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token", + "sharedInbox" => "#{Pleroma.Web.base_url()}/inbox", + "uploadMedia" => "#{Pleroma.Web.base_url()}/api/ap/upload_media" + } + + assert response["@context"] == [ + "https://www.w3.org/ns/activitystreams", + "http://localhost:4001/schemas/litepub-0.1.jsonld", + %{"@language" => "und"} + ] + + assert Map.take(response, [ + "followers", + "following", + "id", + "inbox", + "manuallyApprovesFollowers", + "name", + "outbox", + "preferredUsername", + "summary", + "tag", + "type", + "url" + ]) == %{ + "followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers", + "following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following", + "id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}", + "inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox", + "manuallyApprovesFollowers" => false, + "name" => user.name, + "outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox", + "preferredUsername" => user.nickname, + "summary" => user.bio, + "tag" => [], + "type" => "Person", + "url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}" + } + end + + test "activity+json format. it returns error whe use not found", %{conn: conn} do + response = + conn + |> put_req_header("accept", "application/activity+json") + |> get("/users/jimm") + |> json_response(404) + + assert response == "Not found" + end + + test "json format. it redirects on actual feed of user", %{conn: conn} do + note_activity = insert(:note_activity) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) + + response = + conn + |> put_req_header("accept", "application/json") + |> get("/users/#{user.nickname}") + |> json_response(200) + + assert response["endpoints"] == %{ + "oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize", + "oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps", + "oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token", + "sharedInbox" => "#{Pleroma.Web.base_url()}/inbox", + "uploadMedia" => "#{Pleroma.Web.base_url()}/api/ap/upload_media" + } + + assert response["@context"] == [ + "https://www.w3.org/ns/activitystreams", + "http://localhost:4001/schemas/litepub-0.1.jsonld", + %{"@language" => "und"} + ] + + assert Map.take(response, [ + "followers", + "following", + "id", + "inbox", + "manuallyApprovesFollowers", + "name", + "outbox", + "preferredUsername", + "summary", + "tag", + "type", + "url" + ]) == %{ + "followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers", + "following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following", + "id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}", + "inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox", + "manuallyApprovesFollowers" => false, + "name" => user.name, + "outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox", + "preferredUsername" => user.nickname, + "summary" => user.bio, + "tag" => [], + "type" => "Person", + "url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}" + } + end + + test "json format. it returns error whe use not found", %{conn: conn} do + response = + conn + |> put_req_header("accept", "application/json") + |> get("/users/jimm") + |> json_response(404) + + assert response == "Not found" + end + + test "html format. it redirects on actual feed of user", %{conn: conn} do + note_activity = insert(:note_activity) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) + + response = + conn + |> get("/users/#{user.nickname}") + |> response(200) + + assert response == + Fallback.RedirectController.redirector_with_meta( + conn, + %{user: user} + ).resp_body + end + + test "html format. it returns error when user not found", %{conn: conn} do + response = + conn + |> get("/users/jimm") + |> json_response(404) + + assert response == %{"error" => "Not found"} + end + end +end diff --git a/test/web/instances/instance_test.exs b/test/web/instances/instance_test.exs index 3fd011fd3..a3c93b986 100644 --- a/test/web/instances/instance_test.exs +++ b/test/web/instances/instance_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Instances.InstanceTest do @@ -16,7 +16,8 @@ defmodule Pleroma.Instances.InstanceTest do describe "set_reachable/1" do test "clears `unreachable_since` of existing matching Instance record having non-nil `unreachable_since`" do - instance = insert(:instance, unreachable_since: NaiveDateTime.utc_now()) + unreachable_since = NaiveDateTime.to_iso8601(NaiveDateTime.utc_now()) + instance = insert(:instance, unreachable_since: unreachable_since) assert {:ok, instance} = Instance.set_reachable(instance.host) refute instance.unreachable_since diff --git a/test/web/instances/instances_test.exs b/test/web/instances/instances_test.exs index dea8e2aea..c5d6abc9c 100644 --- a/test/web/instances/instances_test.exs +++ b/test/web/instances/instances_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.InstancesTest do diff --git a/test/web/masto_fe_controller_test.exs b/test/web/masto_fe_controller_test.exs new file mode 100644 index 000000000..9a2d76e0b --- /dev/null +++ b/test/web/masto_fe_controller_test.exs @@ -0,0 +1,85 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.MastoFEController do + use Pleroma.Web.ConnCase + + alias Pleroma.Config + alias Pleroma.User + + import Pleroma.Factory + + clear_config([:instance, :public]) + + test "put settings", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:accounts"])) + |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}}) + + assert _result = json_response(conn, 200) + + user = User.get_cached_by_ap_id(user.ap_id) + assert user.settings == %{"programming" => "socks"} + end + + describe "index/2 redirections" do + setup %{conn: conn} do + session_opts = [ + store: :cookie, + key: "_test", + signing_salt: "cooldude" + ] + + conn = + conn + |> Plug.Session.call(Plug.Session.init(session_opts)) + |> fetch_session() + + test_path = "/web/statuses/test" + %{conn: conn, path: test_path} + end + + test "redirects not logged-in users to the login page", %{conn: conn, path: path} do + conn = get(conn, path) + + assert conn.status == 302 + assert redirected_to(conn) == "/web/login" + end + + test "redirects not logged-in users to the login page on private instances", %{ + conn: conn, + path: path + } do + Config.put([:instance, :public], false) + + conn = get(conn, path) + + assert conn.status == 302 + assert redirected_to(conn) == "/web/login" + end + + test "does not redirect logged in users to the login page", %{conn: conn, path: path} do + token = insert(:oauth_token, scopes: ["read"]) + + conn = + conn + |> assign(:user, token.user) + |> assign(:token, token) + |> get(path) + + assert conn.status == 200 + end + + test "saves referer path to session", %{conn: conn, path: path} do + conn = get(conn, path) + return_to = Plug.Conn.get_session(conn, :return_to) + + assert return_to == path + end + end +end diff --git a/test/web/mastodon_api/controllers/mastodon_api_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs index 56a8f1716..cba68859e 100644 --- a/test/web/mastodon_api/controllers/mastodon_api_controller/update_credentials_test.exs +++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do @@ -12,13 +12,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do clear_config([:instance, :max_account_fields]) describe "updating credentials" do - test "sets user settings in a generic way", %{conn: conn} do - user = insert(:user) + setup do: oauth_access(["write:accounts"]) + test "sets user settings in a generic way", %{conn: conn} do res_conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{ + patch(conn, "/api/v1/accounts/update_credentials", %{ "pleroma_settings_store" => %{ pleroma_fe: %{ theme: "bla" @@ -26,10 +24,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do } }) - assert user = json_response(res_conn, 200) - assert user["pleroma"]["settings_store"] == %{"pleroma_fe" => %{"theme" => "bla"}} + assert user_data = json_response(res_conn, 200) + assert user_data["pleroma"]["settings_store"] == %{"pleroma_fe" => %{"theme" => "bla"}} - user = Repo.get(User, user["id"]) + user = Repo.get(User, user_data["id"]) res_conn = conn @@ -42,15 +40,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do } }) - assert user = json_response(res_conn, 200) + assert user_data = json_response(res_conn, 200) - assert user["pleroma"]["settings_store"] == + assert user_data["pleroma"]["settings_store"] == %{ "pleroma_fe" => %{"theme" => "bla"}, "masto_fe" => %{"theme" => "bla"} } - user = Repo.get(User, user["id"]) + user = Repo.get(User, user_data["id"]) res_conn = conn @@ -63,9 +61,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do } }) - assert user = json_response(res_conn, 200) + assert user_data = json_response(res_conn, 200) - assert user["pleroma"]["settings_store"] == + assert user_data["pleroma"]["settings_store"] == %{ "pleroma_fe" => %{"theme" => "bla"}, "masto_fe" => %{"theme" => "blub"} @@ -73,173 +71,137 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do end test "updates the user's bio", %{conn: conn} do - user = insert(:user) user2 = insert(:user) conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{ + patch(conn, "/api/v1/accounts/update_credentials", %{ "note" => "I drink #cofe with @#{user2.nickname}" }) - assert user = json_response(conn, 200) + assert user_data = json_response(conn, 200) - assert user["note"] == - ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe" rel="tag">#cofe</a> with <span class="h-card"><a data-user=") <> - user2.id <> - ~s(" class="u-url mention" href=") <> - user2.ap_id <> ~s(">@<span>) <> user2.nickname <> ~s(</span></a></span>) + assert user_data["note"] == + ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a data-user="#{ + user2.id + }" class="u-url mention" href="#{user2.ap_id}" rel="ugc">@<span>#{user2.nickname}</span></a></span>) end test "updates the user's locking status", %{conn: conn} do - user = insert(:user) + conn = patch(conn, "/api/v1/accounts/update_credentials", %{locked: "true"}) - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{locked: "true"}) + assert user_data = json_response(conn, 200) + assert user_data["locked"] == true + end + + test "updates the user's allow_following_move", %{user: user, conn: conn} do + assert user.allow_following_move == true + + conn = patch(conn, "/api/v1/accounts/update_credentials", %{allow_following_move: "false"}) - assert user = json_response(conn, 200) - assert user["locked"] == true + assert refresh_record(user).allow_following_move == false + assert user_data = json_response(conn, 200) + assert user_data["pleroma"]["allow_following_move"] == false end test "updates the user's default scope", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{default_scope: "cofe"}) + conn = patch(conn, "/api/v1/accounts/update_credentials", %{default_scope: "cofe"}) - assert user = json_response(conn, 200) - assert user["source"]["privacy"] == "cofe" + assert user_data = json_response(conn, 200) + assert user_data["source"]["privacy"] == "cofe" end test "updates the user's hide_followers status", %{conn: conn} do - user = insert(:user) + conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_followers: "true"}) + assert user_data = json_response(conn, 200) + assert user_data["pleroma"]["hide_followers"] == true + end + + test "updates the user's hide_followers_count and hide_follows_count", %{conn: conn} do conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{hide_followers: "true"}) + patch(conn, "/api/v1/accounts/update_credentials", %{ + hide_followers_count: "true", + hide_follows_count: "true" + }) - assert user = json_response(conn, 200) - assert user["pleroma"]["hide_followers"] == true + assert user_data = json_response(conn, 200) + assert user_data["pleroma"]["hide_followers_count"] == true + assert user_data["pleroma"]["hide_follows_count"] == true end - test "updates the user's skip_thread_containment option", %{conn: conn} do - user = insert(:user) - + test "updates the user's skip_thread_containment option", %{user: user, conn: conn} do response = conn - |> assign(:user, user) |> patch("/api/v1/accounts/update_credentials", %{skip_thread_containment: "true"}) |> json_response(200) assert response["pleroma"]["skip_thread_containment"] == true - assert refresh_record(user).info.skip_thread_containment + assert refresh_record(user).skip_thread_containment end test "updates the user's hide_follows status", %{conn: conn} do - user = insert(:user) + conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_follows: "true"}) - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{hide_follows: "true"}) - - assert user = json_response(conn, 200) - assert user["pleroma"]["hide_follows"] == true + assert user_data = json_response(conn, 200) + assert user_data["pleroma"]["hide_follows"] == true end test "updates the user's hide_favorites status", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{hide_favorites: "true"}) + conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_favorites: "true"}) - assert user = json_response(conn, 200) - assert user["pleroma"]["hide_favorites"] == true + assert user_data = json_response(conn, 200) + assert user_data["pleroma"]["hide_favorites"] == true end test "updates the user's show_role status", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{show_role: "false"}) + conn = patch(conn, "/api/v1/accounts/update_credentials", %{show_role: "false"}) - assert user = json_response(conn, 200) - assert user["source"]["pleroma"]["show_role"] == false + assert user_data = json_response(conn, 200) + assert user_data["source"]["pleroma"]["show_role"] == false end test "updates the user's no_rich_text status", %{conn: conn} do - user = insert(:user) + conn = patch(conn, "/api/v1/accounts/update_credentials", %{no_rich_text: "true"}) - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{no_rich_text: "true"}) - - assert user = json_response(conn, 200) - assert user["source"]["pleroma"]["no_rich_text"] == true + assert user_data = json_response(conn, 200) + assert user_data["source"]["pleroma"]["no_rich_text"] == true end test "updates the user's name", %{conn: conn} do - user = insert(:user) - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"}) + patch(conn, "/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"}) - assert user = json_response(conn, 200) - assert user["display_name"] == "markorepairs" + assert user_data = json_response(conn, 200) + assert user_data["display_name"] == "markorepairs" end - test "updates the user's avatar", %{conn: conn} do - user = insert(:user) - + test "updates the user's avatar", %{user: user, conn: conn} do new_avatar = %Plug.Upload{ content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{"avatar" => new_avatar}) + conn = patch(conn, "/api/v1/accounts/update_credentials", %{"avatar" => new_avatar}) assert user_response = json_response(conn, 200) assert user_response["avatar"] != User.avatar_url(user) end - test "updates the user's banner", %{conn: conn} do - user = insert(:user) - + test "updates the user's banner", %{user: user, conn: conn} do new_header = %Plug.Upload{ content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{"header" => new_header}) + conn = patch(conn, "/api/v1/accounts/update_credentials", %{"header" => new_header}) assert user_response = json_response(conn, 200) assert user_response["header"] != User.banner_url(user) end test "updates the user's background", %{conn: conn} do - user = insert(:user) - new_header = %Plug.Upload{ content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), @@ -247,9 +209,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do } conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{ + patch(conn, "/api/v1/accounts/update_credentials", %{ "pleroma_background_image" => new_header }) @@ -257,71 +217,63 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do assert user_response["pleroma"]["background_image"] end - test "requires 'write' permission", %{conn: conn} do + test "requires 'write:accounts' permission" do token1 = insert(:oauth_token, scopes: ["read"]) token2 = insert(:oauth_token, scopes: ["write", "follow"]) for token <- [token1, token2] do conn = - conn + build_conn() |> put_req_header("authorization", "Bearer #{token.token}") |> patch("/api/v1/accounts/update_credentials", %{}) if token == token1 do - assert %{"error" => "Insufficient permissions: write."} == json_response(conn, 403) + assert %{"error" => "Insufficient permissions: write:accounts."} == + json_response(conn, 403) else assert json_response(conn, 200) end end end - test "updates profile emojos", %{conn: conn} do - user = insert(:user) - + test "updates profile emojos", %{user: user, conn: conn} do note = "*sips :blank:*" name = "I am :firefox:" - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{ + ret_conn = + patch(conn, "/api/v1/accounts/update_credentials", %{ "note" => note, "display_name" => name }) - assert json_response(conn, 200) + assert json_response(ret_conn, 200) - conn = - conn - |> get("/api/v1/accounts/#{user.id}") + conn = get(conn, "/api/v1/accounts/#{user.id}") - assert user = json_response(conn, 200) + assert user_data = json_response(conn, 200) - assert user["note"] == note - assert user["display_name"] == name - assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = user["emojis"] + assert user_data["note"] == note + assert user_data["display_name"] == name + assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = user_data["emojis"] end test "update fields", %{conn: conn} do - user = insert(:user) - fields = [ %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "<script>bar</script>"}, %{"name" => "link", "value" => "cofe.io"} ] - account = + account_data = conn - |> assign(:user, user) |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) |> json_response(200) - assert account["fields"] == [ - %{"name" => "foo", "value" => "bar"}, - %{"name" => "link", "value" => "<a href=\"http://cofe.io\">cofe.io</a>"} + assert account_data["fields"] == [ + %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"}, + %{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)} ] - assert account["source"]["fields"] == [ + assert account_data["source"]["fields"] == [ %{ "name" => "<a href=\"http://google.com\">foo</a>", "value" => "<script>bar</script>" @@ -341,13 +293,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do account = conn |> put_req_header("content-type", "application/x-www-form-urlencoded") - |> assign(:user, user) |> patch("/api/v1/accounts/update_credentials", fields) |> json_response(200) assert account["fields"] == [ - %{"name" => "foo", "value" => "bar"}, - %{"name" => "link", "value" => "<a href=\"http://cofe.io\">cofe.io</a>"} + %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"}, + %{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)} ] assert account["source"]["fields"] == [ @@ -367,7 +318,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do assert %{"error" => "Invalid request"} == conn - |> assign(:user, user) |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) |> json_response(403) @@ -377,7 +327,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do assert %{"error" => "Invalid request"} == conn - |> assign(:user, user) |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) |> json_response(403) @@ -390,7 +339,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do assert %{"error" => "Invalid request"} == conn - |> assign(:user, user) |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) |> json_response(403) @@ -401,7 +349,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do account = conn - |> assign(:user, user) |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) |> json_response(200) diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs new file mode 100644 index 000000000..57d0f4416 --- /dev/null +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -0,0 +1,857 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.InternalFetchActor + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.OAuth.Token + + import Pleroma.Factory + + describe "account fetching" do + clear_config([:instance, :limit_to_local_content]) + + test "works by id" do + user = insert(:user) + + conn = + build_conn() + |> get("/api/v1/accounts/#{user.id}") + + assert %{"id" => id} = json_response(conn, 200) + assert id == to_string(user.id) + + conn = + build_conn() + |> get("/api/v1/accounts/-1") + + assert %{"error" => "Can't find user"} = json_response(conn, 404) + end + + test "works by nickname" do + user = insert(:user) + + conn = + build_conn() + |> get("/api/v1/accounts/#{user.nickname}") + + assert %{"id" => id} = json_response(conn, 200) + assert id == user.id + end + + test "works by nickname for remote users" do + Pleroma.Config.put([:instance, :limit_to_local_content], false) + user = insert(:user, nickname: "user@example.com", local: false) + + conn = + build_conn() + |> get("/api/v1/accounts/#{user.nickname}") + + assert %{"id" => id} = json_response(conn, 200) + assert id == user.id + end + + test "respects limit_to_local_content == :all for remote user nicknames" do + Pleroma.Config.put([:instance, :limit_to_local_content], :all) + + user = insert(:user, nickname: "user@example.com", local: false) + + conn = + build_conn() + |> get("/api/v1/accounts/#{user.nickname}") + + assert json_response(conn, 404) + end + + test "respects limit_to_local_content == :unauthenticated for remote user nicknames" do + Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated) + + user = insert(:user, nickname: "user@example.com", local: false) + reading_user = insert(:user) + + conn = + build_conn() + |> get("/api/v1/accounts/#{user.nickname}") + + assert json_response(conn, 404) + + conn = + build_conn() + |> assign(:user, reading_user) + |> assign(:token, insert(:oauth_token, user: reading_user, scopes: ["read:accounts"])) + |> get("/api/v1/accounts/#{user.nickname}") + + assert %{"id" => id} = json_response(conn, 200) + assert id == user.id + end + + test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do + # Need to set an old-style integer ID to reproduce the problem + # (these are no longer assigned to new accounts but were preserved + # for existing accounts during the migration to flakeIDs) + user_one = insert(:user, %{id: 1212}) + user_two = insert(:user, %{nickname: "#{user_one.id}garbage"}) + + resp_one = + conn + |> get("/api/v1/accounts/#{user_one.id}") + + resp_two = + conn + |> get("/api/v1/accounts/#{user_two.nickname}") + + resp_three = + conn + |> get("/api/v1/accounts/#{user_two.id}") + + acc_one = json_response(resp_one, 200) + acc_two = json_response(resp_two, 200) + acc_three = json_response(resp_three, 200) + refute acc_one == acc_two + assert acc_two == acc_three + end + + test "returns 404 when user is invisible", %{conn: conn} do + user = insert(:user, %{invisible: true}) + + resp = + conn + |> get("/api/v1/accounts/#{user.nickname}") + |> json_response(404) + + assert %{"error" => "Can't find user"} = resp + end + + test "returns 404 for internal.fetch actor", %{conn: conn} do + %User{nickname: "internal.fetch"} = InternalFetchActor.get_actor() + + resp = + conn + |> get("/api/v1/accounts/internal.fetch") + |> json_response(404) + + assert %{"error" => "Can't find user"} = resp + end + end + + describe "user timelines" do + setup do: oauth_access(["read:statuses"]) + + test "respects blocks", %{user: user_one, conn: conn} do + user_two = insert(:user) + user_three = insert(:user) + + User.block(user_one, user_two) + + {:ok, activity} = CommonAPI.post(user_two, %{"status" => "User one sux0rz"}) + {:ok, repeat, _} = CommonAPI.repeat(activity.id, user_three) + + resp = get(conn, "/api/v1/accounts/#{user_two.id}/statuses") + + assert [%{"id" => id}] = json_response(resp, 200) + assert id == activity.id + + # Even a blocked user will deliver the full user timeline, there would be + # no point in looking at a blocked users timeline otherwise + resp = get(conn, "/api/v1/accounts/#{user_two.id}/statuses") + + assert [%{"id" => id}] = json_response(resp, 200) + assert id == activity.id + + # Third user's timeline includes the repeat when viewed by unauthenticated user + resp = get(build_conn(), "/api/v1/accounts/#{user_three.id}/statuses") + assert [%{"id" => id}] = json_response(resp, 200) + assert id == repeat.id + + # When viewing a third user's timeline, the blocked users' statuses will NOT be shown + resp = get(conn, "/api/v1/accounts/#{user_three.id}/statuses") + + assert [] = json_response(resp, 200) + end + + test "gets users statuses", %{conn: conn} do + user_one = insert(:user) + user_two = insert(:user) + user_three = insert(:user) + + {:ok, _user_three} = User.follow(user_three, user_one) + + {:ok, activity} = CommonAPI.post(user_one, %{"status" => "HI!!!"}) + + {:ok, direct_activity} = + CommonAPI.post(user_one, %{ + "status" => "Hi, @#{user_two.nickname}.", + "visibility" => "direct" + }) + + {:ok, private_activity} = + CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"}) + + resp = get(conn, "/api/v1/accounts/#{user_one.id}/statuses") + + assert [%{"id" => id}] = json_response(resp, 200) + assert id == to_string(activity.id) + + resp = + conn + |> assign(:user, user_two) + |> assign(:token, insert(:oauth_token, user: user_two, scopes: ["read:statuses"])) + |> get("/api/v1/accounts/#{user_one.id}/statuses") + + assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200) + assert id_one == to_string(direct_activity.id) + assert id_two == to_string(activity.id) + + resp = + conn + |> assign(:user, user_three) + |> assign(:token, insert(:oauth_token, user: user_three, scopes: ["read:statuses"])) + |> get("/api/v1/accounts/#{user_one.id}/statuses") + + assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200) + assert id_one == to_string(private_activity.id) + assert id_two == to_string(activity.id) + end + + test "unimplemented pinned statuses feature", %{conn: conn} do + note = insert(:note_activity) + user = User.get_cached_by_ap_id(note.data["actor"]) + + conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?pinned=true") + + assert json_response(conn, 200) == [] + end + + test "gets an users media", %{conn: conn} do + note = insert(:note_activity) + user = User.get_cached_by_ap_id(note.data["actor"]) + + file = %Plug.Upload{ + content_type: "image/jpg", + path: Path.absname("test/fixtures/image.jpg"), + filename: "an_image.jpg" + } + + {:ok, %{id: media_id}} = ActivityPub.upload(file, actor: user.ap_id) + + {:ok, image_post} = CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media_id]}) + + conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"}) + + assert [%{"id" => id}] = json_response(conn, 200) + assert id == to_string(image_post.id) + + conn = get(build_conn(), "/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"}) + + assert [%{"id" => id}] = json_response(conn, 200) + assert id == to_string(image_post.id) + end + + test "gets a user's statuses without reblogs", %{user: user, conn: conn} do + {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"}) + {:ok, _, _} = CommonAPI.repeat(post.id, user) + + conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"}) + + assert [%{"id" => id}] = json_response(conn, 200) + assert id == to_string(post.id) + + conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"}) + + assert [%{"id" => id}] = json_response(conn, 200) + assert id == to_string(post.id) + end + + test "filters user's statuses by a hashtag", %{user: user, conn: conn} do + {:ok, post} = CommonAPI.post(user, %{"status" => "#hashtag"}) + {:ok, _post} = CommonAPI.post(user, %{"status" => "hashtag"}) + + conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"tagged" => "hashtag"}) + + assert [%{"id" => id}] = json_response(conn, 200) + assert id == to_string(post.id) + end + + test "the user views their own timelines and excludes direct messages", %{ + user: user, + conn: conn + } do + {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"}) + {:ok, _direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) + + conn = + get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"exclude_visibilities" => ["direct"]}) + + assert [%{"id" => id}] = json_response(conn, 200) + assert id == to_string(public_activity.id) + end + end + + describe "followers" do + setup do: oauth_access(["read:accounts"]) + + test "getting followers", %{user: user, conn: conn} do + other_user = insert(:user) + {:ok, user} = User.follow(user, other_user) + + conn = get(conn, "/api/v1/accounts/#{other_user.id}/followers") + + assert [%{"id" => id}] = json_response(conn, 200) + assert id == to_string(user.id) + end + + test "getting followers, hide_followers", %{user: user, conn: conn} do + other_user = insert(:user, hide_followers: true) + {:ok, _user} = User.follow(user, other_user) + + conn = get(conn, "/api/v1/accounts/#{other_user.id}/followers") + + assert [] == json_response(conn, 200) + end + + test "getting followers, hide_followers, same user requesting" do + user = insert(:user) + other_user = insert(:user, hide_followers: true) + {:ok, _user} = User.follow(user, other_user) + + conn = + build_conn() + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"])) + |> get("/api/v1/accounts/#{other_user.id}/followers") + + refute [] == json_response(conn, 200) + end + + test "getting followers, pagination", %{user: user, conn: conn} do + follower1 = insert(:user) + follower2 = insert(:user) + follower3 = insert(:user) + {:ok, _} = User.follow(follower1, user) + {:ok, _} = User.follow(follower2, user) + {:ok, _} = User.follow(follower3, user) + + res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}") + + assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200) + assert id3 == follower3.id + assert id2 == follower2.id + + res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}") + + assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200) + assert id2 == follower2.id + assert id1 == follower1.id + + res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}") + + assert [%{"id" => id2}] = json_response(res_conn, 200) + assert id2 == follower2.id + + assert [link_header] = get_resp_header(res_conn, "link") + assert link_header =~ ~r/min_id=#{follower2.id}/ + assert link_header =~ ~r/max_id=#{follower2.id}/ + end + end + + describe "following" do + setup do: oauth_access(["read:accounts"]) + + test "getting following", %{user: user, conn: conn} do + other_user = insert(:user) + {:ok, user} = User.follow(user, other_user) + + conn = get(conn, "/api/v1/accounts/#{user.id}/following") + + assert [%{"id" => id}] = json_response(conn, 200) + assert id == to_string(other_user.id) + end + + test "getting following, hide_follows, other user requesting" do + user = insert(:user, hide_follows: true) + other_user = insert(:user) + {:ok, user} = User.follow(user, other_user) + + conn = + build_conn() + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"])) + |> get("/api/v1/accounts/#{user.id}/following") + + assert [] == json_response(conn, 200) + end + + test "getting following, hide_follows, same user requesting" do + user = insert(:user, hide_follows: true) + other_user = insert(:user) + {:ok, user} = User.follow(user, other_user) + + conn = + build_conn() + |> assign(:user, user) + |> assign(:token, insert(:oauth_token, user: user, scopes: ["read:accounts"])) + |> get("/api/v1/accounts/#{user.id}/following") + + refute [] == json_response(conn, 200) + end + + test "getting following, pagination", %{user: user, conn: conn} do + following1 = insert(:user) + following2 = insert(:user) + following3 = insert(:user) + {:ok, _} = User.follow(user, following1) + {:ok, _} = User.follow(user, following2) + {:ok, _} = User.follow(user, following3) + + res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}") + + assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200) + assert id3 == following3.id + assert id2 == following2.id + + res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}") + + assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200) + assert id2 == following2.id + assert id1 == following1.id + + res_conn = + get(conn, "/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}") + + assert [%{"id" => id2}] = json_response(res_conn, 200) + assert id2 == following2.id + + assert [link_header] = get_resp_header(res_conn, "link") + assert link_header =~ ~r/min_id=#{following2.id}/ + assert link_header =~ ~r/max_id=#{following2.id}/ + end + end + + describe "follow/unfollow" do + setup do: oauth_access(["follow"]) + + test "following / unfollowing a user", %{conn: conn} do + other_user = insert(:user) + + ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/follow") + + assert %{"id" => _id, "following" => true} = json_response(ret_conn, 200) + + ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/unfollow") + + assert %{"id" => _id, "following" => false} = json_response(ret_conn, 200) + + conn = post(conn, "/api/v1/follows", %{"uri" => other_user.nickname}) + + assert %{"id" => id} = json_response(conn, 200) + assert id == to_string(other_user.id) + end + + test "cancelling follow request", %{conn: conn} do + %{id: other_user_id} = insert(:user, %{locked: true}) + + assert %{"id" => ^other_user_id, "following" => false, "requested" => true} = + conn |> post("/api/v1/accounts/#{other_user_id}/follow") |> json_response(:ok) + + assert %{"id" => ^other_user_id, "following" => false, "requested" => false} = + conn |> post("/api/v1/accounts/#{other_user_id}/unfollow") |> json_response(:ok) + end + + test "following without reblogs" do + %{conn: conn} = oauth_access(["follow", "read:statuses"]) + followed = insert(:user) + other_user = insert(:user) + + ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow?reblogs=false") + + assert %{"showing_reblogs" => false} = json_response(ret_conn, 200) + + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"}) + {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed) + + ret_conn = get(conn, "/api/v1/timelines/home") + + assert [] == json_response(ret_conn, 200) + + ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow?reblogs=true") + + assert %{"showing_reblogs" => true} = json_response(ret_conn, 200) + + conn = get(conn, "/api/v1/timelines/home") + + expected_activity_id = reblog.id + assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200) + end + + test "following / unfollowing errors", %{user: user, conn: conn} do + # self follow + conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow") + assert %{"error" => "Record not found"} = json_response(conn_res, 404) + + # self unfollow + user = User.get_cached_by_id(user.id) + conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow") + assert %{"error" => "Record not found"} = json_response(conn_res, 404) + + # self follow via uri + user = User.get_cached_by_id(user.id) + conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname}) + assert %{"error" => "Record not found"} = json_response(conn_res, 404) + + # follow non existing user + conn_res = post(conn, "/api/v1/accounts/doesntexist/follow") + assert %{"error" => "Record not found"} = json_response(conn_res, 404) + + # follow non existing user via uri + conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"}) + assert %{"error" => "Record not found"} = json_response(conn_res, 404) + + # unfollow non existing user + conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow") + assert %{"error" => "Record not found"} = json_response(conn_res, 404) + end + end + + describe "mute/unmute" do + setup do: oauth_access(["write:mutes"]) + + test "with notifications", %{conn: conn} do + other_user = insert(:user) + + ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/mute") + + response = json_response(ret_conn, 200) + + assert %{"id" => _id, "muting" => true, "muting_notifications" => true} = response + + conn = post(conn, "/api/v1/accounts/#{other_user.id}/unmute") + + response = json_response(conn, 200) + assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response + end + + test "without notifications", %{conn: conn} do + other_user = insert(:user) + + ret_conn = + post(conn, "/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"}) + + response = json_response(ret_conn, 200) + + assert %{"id" => _id, "muting" => true, "muting_notifications" => false} = response + + conn = post(conn, "/api/v1/accounts/#{other_user.id}/unmute") + + response = json_response(conn, 200) + assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response + end + end + + describe "pinned statuses" do + setup do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"}) + %{conn: conn} = oauth_access(["read:statuses"], user: user) + + [conn: conn, user: user, activity: activity] + end + + test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do + {:ok, _} = CommonAPI.pin(activity.id, user) + + result = + conn + |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") + |> json_response(200) + + id_str = to_string(activity.id) + + assert [%{"id" => ^id_str, "pinned" => true}] = result + end + end + + test "blocking / unblocking a user" do + %{conn: conn} = oauth_access(["follow"]) + other_user = insert(:user) + + ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/block") + + assert %{"id" => _id, "blocking" => true} = json_response(ret_conn, 200) + + conn = post(conn, "/api/v1/accounts/#{other_user.id}/unblock") + + assert %{"id" => _id, "blocking" => false} = json_response(conn, 200) + end + + describe "create account by app" do + setup do + valid_params = %{ + username: "lain", + email: "lain@example.org", + password: "PlzDontHackLain", + agreement: true + } + + [valid_params: valid_params] + end + + test "Account registration via Application", %{conn: conn} do + conn = + post(conn, "/api/v1/apps", %{ + client_name: "client_name", + redirect_uris: "urn:ietf:wg:oauth:2.0:oob", + scopes: "read, write, follow" + }) + + %{ + "client_id" => client_id, + "client_secret" => client_secret, + "id" => _, + "name" => "client_name", + "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob", + "vapid_key" => _, + "website" => nil + } = json_response(conn, 200) + + conn = + post(conn, "/oauth/token", %{ + grant_type: "client_credentials", + client_id: client_id, + client_secret: client_secret + }) + + assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} = + json_response(conn, 200) + + assert token + token_from_db = Repo.get_by(Token, token: token) + assert token_from_db + assert refresh + assert scope == "read write follow" + + conn = + build_conn() + |> put_req_header("authorization", "Bearer " <> token) + |> post("/api/v1/accounts", %{ + username: "lain", + email: "lain@example.org", + password: "PlzDontHackLain", + bio: "Test Bio", + agreement: true + }) + + %{ + "access_token" => token, + "created_at" => _created_at, + "scope" => _scope, + "token_type" => "Bearer" + } = json_response(conn, 200) + + token_from_db = Repo.get_by(Token, token: token) + assert token_from_db + token_from_db = Repo.preload(token_from_db, :user) + assert token_from_db.user + + assert token_from_db.user.confirmation_pending + end + + test "returns error when user already registred", %{conn: conn, valid_params: valid_params} do + _user = insert(:user, email: "lain@example.org") + app_token = insert(:oauth_token, user: nil) + + conn = + conn + |> put_req_header("authorization", "Bearer " <> app_token.token) + + res = post(conn, "/api/v1/accounts", valid_params) + assert json_response(res, 400) == %{"error" => "{\"email\":[\"has already been taken\"]}"} + end + + test "returns bad_request if missing required params", %{ + conn: conn, + valid_params: valid_params + } do + app_token = insert(:oauth_token, user: nil) + + conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token) + + res = post(conn, "/api/v1/accounts", valid_params) + assert json_response(res, 200) + + [{127, 0, 0, 1}, {127, 0, 0, 2}, {127, 0, 0, 3}, {127, 0, 0, 4}] + |> Stream.zip(valid_params) + |> Enum.each(fn {ip, {attr, _}} -> + res = + conn + |> Map.put(:remote_ip, ip) + |> post("/api/v1/accounts", Map.delete(valid_params, attr)) + |> json_response(400) + + assert res == %{"error" => "Missing parameters"} + end) + end + + test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_params} do + conn = put_req_header(conn, "authorization", "Bearer " <> "invalid-token") + + res = post(conn, "/api/v1/accounts", valid_params) + assert json_response(res, 403) == %{"error" => "Invalid credentials"} + end + end + + describe "create account by app / rate limit" do + clear_config([Pleroma.Plugs.RemoteIp, :enabled]) do + Pleroma.Config.put([Pleroma.Plugs.RemoteIp, :enabled], true) + end + + clear_config([:rate_limit, :app_account_creation]) do + Pleroma.Config.put([:rate_limit, :app_account_creation], {10_000, 2}) + end + + test "respects rate limit setting", %{conn: conn} do + app_token = insert(:oauth_token, user: nil) + + conn = + conn + |> put_req_header("authorization", "Bearer " <> app_token.token) + |> Map.put(:remote_ip, {15, 15, 15, 15}) + + for i <- 1..2 do + conn = + post(conn, "/api/v1/accounts", %{ + username: "#{i}lain", + email: "#{i}lain@example.org", + password: "PlzDontHackLain", + agreement: true + }) + + %{ + "access_token" => token, + "created_at" => _created_at, + "scope" => _scope, + "token_type" => "Bearer" + } = json_response(conn, 200) + + token_from_db = Repo.get_by(Token, token: token) + assert token_from_db + token_from_db = Repo.preload(token_from_db, :user) + assert token_from_db.user + + assert token_from_db.user.confirmation_pending + end + + conn = + post(conn, "/api/v1/accounts", %{ + username: "6lain", + email: "6lain@example.org", + password: "PlzDontHackLain", + agreement: true + }) + + assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"} + end + end + + describe "GET /api/v1/accounts/:id/lists - account_lists" do + test "returns lists to which the account belongs" do + %{user: user, conn: conn} = oauth_access(["read:lists"]) + other_user = insert(:user) + assert {:ok, %Pleroma.List{} = list} = Pleroma.List.create("Test List", user) + {:ok, %{following: _following}} = Pleroma.List.follow(list, other_user) + + res = + conn + |> get("/api/v1/accounts/#{other_user.id}/lists") + |> json_response(200) + + assert res == [%{"id" => to_string(list.id), "title" => "Test List"}] + end + end + + describe "verify_credentials" do + test "verify_credentials" do + %{user: user, conn: conn} = oauth_access(["read:accounts"]) + conn = get(conn, "/api/v1/accounts/verify_credentials") + + response = json_response(conn, 200) + + assert %{"id" => id, "source" => %{"privacy" => "public"}} = response + assert response["pleroma"]["chat_token"] + assert id == to_string(user.id) + end + + test "verify_credentials default scope unlisted" do + user = insert(:user, default_scope: "unlisted") + %{conn: conn} = oauth_access(["read:accounts"], user: user) + + conn = get(conn, "/api/v1/accounts/verify_credentials") + + assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200) + assert id == to_string(user.id) + end + + test "locked accounts" do + user = insert(:user, default_scope: "private") + %{conn: conn} = oauth_access(["read:accounts"], user: user) + + conn = get(conn, "/api/v1/accounts/verify_credentials") + + assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200) + assert id == to_string(user.id) + end + end + + describe "user relationships" do + setup do: oauth_access(["read:follows"]) + + test "returns the relationships for the current user", %{user: user, conn: conn} do + other_user = insert(:user) + {:ok, _user} = User.follow(user, other_user) + + conn = get(conn, "/api/v1/accounts/relationships", %{"id" => [other_user.id]}) + + assert [relationship] = json_response(conn, 200) + + assert to_string(other_user.id) == relationship["id"] + end + + test "returns an empty list on a bad request", %{conn: conn} do + conn = get(conn, "/api/v1/accounts/relationships", %{}) + + assert [] = json_response(conn, 200) + end + end + + test "getting a list of mutes" do + %{user: user, conn: conn} = oauth_access(["read:mutes"]) + other_user = insert(:user) + + {:ok, _user_relationships} = User.mute(user, other_user) + + conn = get(conn, "/api/v1/mutes") + + other_user_id = to_string(other_user.id) + assert [%{"id" => ^other_user_id}] = json_response(conn, 200) + end + + test "getting a list of blocks" do + %{user: user, conn: conn} = oauth_access(["read:blocks"]) + other_user = insert(:user) + + {:ok, _user_relationship} = User.block(user, other_user) + + conn = + conn + |> assign(:user, user) + |> get("/api/v1/blocks") + + other_user_id = to_string(other_user.id) + assert [%{"id" => ^other_user_id}] = json_response(conn, 200) + end +end diff --git a/test/web/mastodon_api/controllers/app_controller_test.exs b/test/web/mastodon_api/controllers/app_controller_test.exs new file mode 100644 index 000000000..77d234d67 --- /dev/null +++ b/test/web/mastodon_api/controllers/app_controller_test.exs @@ -0,0 +1,60 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.AppControllerTest do + use Pleroma.Web.ConnCase, async: true + + alias Pleroma.Repo + alias Pleroma.Web.OAuth.App + alias Pleroma.Web.Push + + import Pleroma.Factory + + test "apps/verify_credentials", %{conn: conn} do + token = insert(:oauth_token) + + conn = + conn + |> assign(:user, token.user) + |> assign(:token, token) + |> get("/api/v1/apps/verify_credentials") + + app = Repo.preload(token, :app).app + + expected = %{ + "name" => app.client_name, + "website" => app.website, + "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key) + } + + assert expected == json_response(conn, 200) + end + + test "creates an oauth app", %{conn: conn} do + user = insert(:user) + app_attrs = build(:oauth_app) + + conn = + conn + |> assign(:user, user) + |> post("/api/v1/apps", %{ + client_name: app_attrs.client_name, + redirect_uris: app_attrs.redirect_uris + }) + + [app] = Repo.all(App) + + expected = %{ + "name" => app.client_name, + "website" => app.website, + "client_id" => app.client_id, + "client_secret" => app.client_secret, + "id" => app.id |> to_string(), + "redirect_uri" => app.redirect_uris, + "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key) + } + + assert expected == json_response(conn, 200) + end +end diff --git a/test/web/mastodon_api/controllers/auth_controller_test.exs b/test/web/mastodon_api/controllers/auth_controller_test.exs new file mode 100644 index 000000000..a485f8e41 --- /dev/null +++ b/test/web/mastodon_api/controllers/auth_controller_test.exs @@ -0,0 +1,152 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.AuthControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Config + alias Pleroma.Repo + alias Pleroma.Tests.ObanHelpers + + import Pleroma.Factory + import Swoosh.TestAssertions + + describe "GET /web/login" do + setup %{conn: conn} do + session_opts = [ + store: :cookie, + key: "_test", + signing_salt: "cooldude" + ] + + conn = + conn + |> Plug.Session.call(Plug.Session.init(session_opts)) + |> fetch_session() + + test_path = "/web/statuses/test" + %{conn: conn, path: test_path} + end + + test "redirects to the saved path after log in", %{conn: conn, path: path} do + app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") + auth = insert(:oauth_authorization, app: app) + + conn = + conn + |> put_session(:return_to, path) + |> get("/web/login", %{code: auth.token}) + + assert conn.status == 302 + assert redirected_to(conn) == path + end + + test "redirects to the getting-started page when referer is not present", %{conn: conn} do + app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") + auth = insert(:oauth_authorization, app: app) + + conn = get(conn, "/web/login", %{code: auth.token}) + + assert conn.status == 302 + assert redirected_to(conn) == "/web/getting-started" + end + end + + describe "POST /auth/password, with valid parameters" do + setup %{conn: conn} do + user = insert(:user) + conn = post(conn, "/auth/password?email=#{user.email}") + %{conn: conn, user: user} + end + + test "it returns 204", %{conn: conn} do + assert json_response(conn, :no_content) + end + + test "it creates a PasswordResetToken record for user", %{user: user} do + token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) + assert token_record + end + + test "it sends an email to user", %{user: user} do + ObanHelpers.perform_all() + token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) + + email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token) + notify_email = Config.get([:instance, :notify_email]) + instance_name = Config.get([:instance, :name]) + + assert_email_sent( + from: {instance_name, notify_email}, + to: {user.name, user.email}, + html_body: email.html_body + ) + end + end + + describe "POST /auth/password, with nickname" do + test "it returns 204", %{conn: conn} do + user = insert(:user) + + assert conn + |> post("/auth/password?nickname=#{user.nickname}") + |> json_response(:no_content) + + ObanHelpers.perform_all() + token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) + + email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token) + notify_email = Config.get([:instance, :notify_email]) + instance_name = Config.get([:instance, :name]) + + assert_email_sent( + from: {instance_name, notify_email}, + to: {user.name, user.email}, + html_body: email.html_body + ) + end + + test "it doesn't fail when a user has no email", %{conn: conn} do + user = insert(:user, %{email: nil}) + + assert conn + |> post("/auth/password?nickname=#{user.nickname}") + |> json_response(:no_content) + end + end + + describe "POST /auth/password, with invalid parameters" do + setup do + user = insert(:user) + {:ok, user: user} + end + + test "it returns 404 when user is not found", %{conn: conn, user: user} do + conn = post(conn, "/auth/password?email=nonexisting_#{user.email}") + assert conn.status == 404 + assert conn.resp_body == "" + end + + test "it returns 400 when user is not local", %{conn: conn, user: user} do + {:ok, user} = Repo.update(Ecto.Changeset.change(user, local: false)) + conn = post(conn, "/auth/password?email=#{user.email}") + assert conn.status == 400 + assert conn.resp_body == "" + end + end + + describe "DELETE /auth/sign_out" do + test "redirect to root page", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> delete("/auth/sign_out") + + assert conn.status == 302 + assert redirected_to(conn) == "/" + end + end +end diff --git a/test/web/mastodon_api/controllers/conversation_controller_test.exs b/test/web/mastodon_api/controllers/conversation_controller_test.exs new file mode 100644 index 000000000..801b0259b --- /dev/null +++ b/test/web/mastodon_api/controllers/conversation_controller_test.exs @@ -0,0 +1,208 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.User + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + setup do: oauth_access(["read:statuses"]) + + test "returns a list of conversations", %{user: user_one, conn: conn} do + user_two = insert(:user) + user_three = insert(:user) + + {:ok, user_two} = User.follow(user_two, user_one) + + assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0 + + {:ok, direct} = + CommonAPI.post(user_one, %{ + "status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!", + "visibility" => "direct" + }) + + assert User.get_cached_by_id(user_two.id).unread_conversation_count == 1 + + {:ok, _follower_only} = + CommonAPI.post(user_one, %{ + "status" => "Hi @#{user_two.nickname}!", + "visibility" => "private" + }) + + res_conn = get(conn, "/api/v1/conversations") + + assert response = json_response(res_conn, 200) + + assert [ + %{ + "id" => res_id, + "accounts" => res_accounts, + "last_status" => res_last_status, + "unread" => unread + } + ] = response + + account_ids = Enum.map(res_accounts, & &1["id"]) + assert length(res_accounts) == 2 + assert user_two.id in account_ids + assert user_three.id in account_ids + assert is_binary(res_id) + assert unread == false + assert res_last_status["id"] == direct.id + assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0 + end + + test "filters conversations by recipients", %{user: user_one, conn: conn} do + user_two = insert(:user) + user_three = insert(:user) + + {:ok, direct1} = + CommonAPI.post(user_one, %{ + "status" => "Hi @#{user_two.nickname}!", + "visibility" => "direct" + }) + + {:ok, _direct2} = + CommonAPI.post(user_one, %{ + "status" => "Hi @#{user_three.nickname}!", + "visibility" => "direct" + }) + + {:ok, direct3} = + CommonAPI.post(user_one, %{ + "status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!", + "visibility" => "direct" + }) + + {:ok, _direct4} = + CommonAPI.post(user_two, %{ + "status" => "Hi @#{user_three.nickname}!", + "visibility" => "direct" + }) + + {:ok, direct5} = + CommonAPI.post(user_two, %{ + "status" => "Hi @#{user_one.nickname}!", + "visibility" => "direct" + }) + + [conversation1, conversation2] = + conn + |> get("/api/v1/conversations", %{"recipients" => [user_two.id]}) + |> json_response(200) + + assert conversation1["last_status"]["id"] == direct5.id + assert conversation2["last_status"]["id"] == direct1.id + + [conversation1] = + conn + |> get("/api/v1/conversations", %{"recipients" => [user_two.id, user_three.id]}) + |> json_response(200) + + assert conversation1["last_status"]["id"] == direct3.id + end + + test "updates the last_status on reply", %{user: user_one, conn: conn} do + user_two = insert(:user) + + {:ok, direct} = + CommonAPI.post(user_one, %{ + "status" => "Hi @#{user_two.nickname}", + "visibility" => "direct" + }) + + {:ok, direct_reply} = + CommonAPI.post(user_two, %{ + "status" => "reply", + "visibility" => "direct", + "in_reply_to_status_id" => direct.id + }) + + [%{"last_status" => res_last_status}] = + conn + |> get("/api/v1/conversations") + |> json_response(200) + + assert res_last_status["id"] == direct_reply.id + end + + test "the user marks a conversation as read", %{user: user_one, conn: conn} do + user_two = insert(:user) + + {:ok, direct} = + CommonAPI.post(user_one, %{ + "status" => "Hi @#{user_two.nickname}", + "visibility" => "direct" + }) + + assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0 + assert User.get_cached_by_id(user_two.id).unread_conversation_count == 1 + + user_two_conn = + build_conn() + |> assign(:user, user_two) + |> assign( + :token, + insert(:oauth_token, user: user_two, scopes: ["read:statuses", "write:conversations"]) + ) + + [%{"id" => direct_conversation_id, "unread" => true}] = + user_two_conn + |> get("/api/v1/conversations") + |> json_response(200) + + %{"unread" => false} = + user_two_conn + |> post("/api/v1/conversations/#{direct_conversation_id}/read") + |> json_response(200) + + assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0 + assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0 + + # The conversation is marked as unread on reply + {:ok, _} = + CommonAPI.post(user_two, %{ + "status" => "reply", + "visibility" => "direct", + "in_reply_to_status_id" => direct.id + }) + + [%{"unread" => true}] = + conn + |> get("/api/v1/conversations") + |> json_response(200) + + assert User.get_cached_by_id(user_one.id).unread_conversation_count == 1 + assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0 + + # A reply doesn't increment the user's unread_conversation_count if the conversation is unread + {:ok, _} = + CommonAPI.post(user_two, %{ + "status" => "reply", + "visibility" => "direct", + "in_reply_to_status_id" => direct.id + }) + + assert User.get_cached_by_id(user_one.id).unread_conversation_count == 1 + assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0 + end + + test "(vanilla) Mastodon frontend behaviour", %{user: user_one, conn: conn} do + user_two = insert(:user) + + {:ok, direct} = + CommonAPI.post(user_one, %{ + "status" => "Hi @#{user_two.nickname}!", + "visibility" => "direct" + }) + + res_conn = get(conn, "/api/v1/statuses/#{direct.id}/context") + + assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200) + end +end diff --git a/test/web/mastodon_api/controllers/custom_emoji_controller_test.exs b/test/web/mastodon_api/controllers/custom_emoji_controller_test.exs new file mode 100644 index 000000000..6567a0667 --- /dev/null +++ b/test/web/mastodon_api/controllers/custom_emoji_controller_test.exs @@ -0,0 +1,22 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.CustomEmojiControllerTest do + use Pleroma.Web.ConnCase, async: true + + test "with tags", %{conn: conn} do + [emoji | _body] = + conn + |> get("/api/v1/custom_emojis") + |> json_response(200) + + assert Map.has_key?(emoji, "shortcode") + assert Map.has_key?(emoji, "static_url") + assert Map.has_key?(emoji, "tags") + assert is_list(emoji["tags"]) + assert Map.has_key?(emoji, "category") + assert Map.has_key?(emoji, "url") + assert Map.has_key?(emoji, "visible_in_picker") + end +end diff --git a/test/web/mastodon_api/controllers/domain_block_controller_test.exs b/test/web/mastodon_api/controllers/domain_block_controller_test.exs new file mode 100644 index 000000000..8d24b3b88 --- /dev/null +++ b/test/web/mastodon_api/controllers/domain_block_controller_test.exs @@ -0,0 +1,45 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.DomainBlockControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.User + + import Pleroma.Factory + + test "blocking / unblocking a domain" do + %{user: user, conn: conn} = oauth_access(["write:blocks"]) + other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"}) + + ret_conn = post(conn, "/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) + + assert %{} = json_response(ret_conn, 200) + user = User.get_cached_by_ap_id(user.ap_id) + assert User.blocks?(user, other_user) + + ret_conn = delete(conn, "/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) + + assert %{} = json_response(ret_conn, 200) + user = User.get_cached_by_ap_id(user.ap_id) + refute User.blocks?(user, other_user) + end + + test "getting a list of domain blocks" do + %{user: user, conn: conn} = oauth_access(["read:blocks"]) + + {:ok, user} = User.block_domain(user, "bad.site") + {:ok, user} = User.block_domain(user, "even.worse.site") + + conn = + conn + |> assign(:user, user) + |> get("/api/v1/domain_blocks") + + domain_blocks = json_response(conn, 200) + + assert "bad.site" in domain_blocks + assert "even.worse.site" in domain_blocks + end +end diff --git a/test/web/mastodon_api/controllers/filter_controller_test.exs b/test/web/mastodon_api/controllers/filter_controller_test.exs new file mode 100644 index 000000000..97ab005e0 --- /dev/null +++ b/test/web/mastodon_api/controllers/filter_controller_test.exs @@ -0,0 +1,123 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Web.MastodonAPI.FilterView + + test "creating a filter" do + %{conn: conn} = oauth_access(["write:filters"]) + + filter = %Pleroma.Filter{ + phrase: "knights", + context: ["home"] + } + + conn = post(conn, "/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context}) + + assert response = json_response(conn, 200) + assert response["phrase"] == filter.phrase + assert response["context"] == filter.context + assert response["irreversible"] == false + assert response["id"] != nil + assert response["id"] != "" + end + + test "fetching a list of filters" do + %{user: user, conn: conn} = oauth_access(["read:filters"]) + + query_one = %Pleroma.Filter{ + user_id: user.id, + filter_id: 1, + phrase: "knights", + context: ["home"] + } + + query_two = %Pleroma.Filter{ + user_id: user.id, + filter_id: 2, + phrase: "who", + context: ["home"] + } + + {:ok, filter_one} = Pleroma.Filter.create(query_one) + {:ok, filter_two} = Pleroma.Filter.create(query_two) + + response = + conn + |> get("/api/v1/filters") + |> json_response(200) + + assert response == + render_json( + FilterView, + "filters.json", + filters: [filter_two, filter_one] + ) + end + + test "get a filter" do + %{user: user, conn: conn} = oauth_access(["read:filters"]) + + query = %Pleroma.Filter{ + user_id: user.id, + filter_id: 2, + phrase: "knight", + context: ["home"] + } + + {:ok, filter} = Pleroma.Filter.create(query) + + conn = get(conn, "/api/v1/filters/#{filter.filter_id}") + + assert _response = json_response(conn, 200) + end + + test "update a filter" do + %{user: user, conn: conn} = oauth_access(["write:filters"]) + + query = %Pleroma.Filter{ + user_id: user.id, + filter_id: 2, + phrase: "knight", + context: ["home"] + } + + {:ok, _filter} = Pleroma.Filter.create(query) + + new = %Pleroma.Filter{ + phrase: "nii", + context: ["home"] + } + + conn = + put(conn, "/api/v1/filters/#{query.filter_id}", %{ + phrase: new.phrase, + context: new.context + }) + + assert response = json_response(conn, 200) + assert response["phrase"] == new.phrase + assert response["context"] == new.context + end + + test "delete a filter" do + %{user: user, conn: conn} = oauth_access(["write:filters"]) + + query = %Pleroma.Filter{ + user_id: user.id, + filter_id: 2, + phrase: "knight", + context: ["home"] + } + + {:ok, filter} = Pleroma.Filter.create(query) + + conn = delete(conn, "/api/v1/filters/#{filter.filter_id}") + + assert response = json_response(conn, 200) + assert response == %{} + end +end diff --git a/test/web/mastodon_api/controllers/follow_request_controller_test.exs b/test/web/mastodon_api/controllers/follow_request_controller_test.exs new file mode 100644 index 000000000..dd848821a --- /dev/null +++ b/test/web/mastodon_api/controllers/follow_request_controller_test.exs @@ -0,0 +1,74 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + + import Pleroma.Factory + + describe "locked accounts" do + setup do + user = insert(:user, locked: true) + %{conn: conn} = oauth_access(["follow"], user: user) + %{user: user, conn: conn} + end + + test "/api/v1/follow_requests works", %{user: user, conn: conn} do + other_user = insert(:user) + + {:ok, _activity} = ActivityPub.follow(other_user, user) + {:ok, other_user} = User.follow(other_user, user, "pending") + + assert User.following?(other_user, user) == false + + conn = get(conn, "/api/v1/follow_requests") + + assert [relationship] = json_response(conn, 200) + assert to_string(other_user.id) == relationship["id"] + end + + test "/api/v1/follow_requests/:id/authorize works", %{user: user, conn: conn} do + other_user = insert(:user) + + {:ok, _activity} = ActivityPub.follow(other_user, user) + {:ok, other_user} = User.follow(other_user, user, "pending") + + user = User.get_cached_by_id(user.id) + other_user = User.get_cached_by_id(other_user.id) + + assert User.following?(other_user, user) == false + + conn = post(conn, "/api/v1/follow_requests/#{other_user.id}/authorize") + + assert relationship = json_response(conn, 200) + assert to_string(other_user.id) == relationship["id"] + + user = User.get_cached_by_id(user.id) + other_user = User.get_cached_by_id(other_user.id) + + assert User.following?(other_user, user) == true + end + + test "/api/v1/follow_requests/:id/reject works", %{user: user, conn: conn} do + other_user = insert(:user) + + {:ok, _activity} = ActivityPub.follow(other_user, user) + + user = User.get_cached_by_id(user.id) + + conn = post(conn, "/api/v1/follow_requests/#{other_user.id}/reject") + + assert relationship = json_response(conn, 200) + assert to_string(other_user.id) == relationship["id"] + + user = User.get_cached_by_id(user.id) + other_user = User.get_cached_by_id(other_user.id) + + assert User.following?(other_user, user) == false + end + end +end diff --git a/test/web/mastodon_api/controllers/instance_controller_test.exs b/test/web/mastodon_api/controllers/instance_controller_test.exs new file mode 100644 index 000000000..2737dcaba --- /dev/null +++ b/test/web/mastodon_api/controllers/instance_controller_test.exs @@ -0,0 +1,77 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.User + import Pleroma.Factory + + test "get instance information", %{conn: conn} do + conn = get(conn, "/api/v1/instance") + assert result = json_response(conn, 200) + + email = Pleroma.Config.get([:instance, :email]) + # Note: not checking for "max_toot_chars" since it's optional + assert %{ + "uri" => _, + "title" => _, + "description" => _, + "version" => _, + "email" => from_config_email, + "urls" => %{ + "streaming_api" => _ + }, + "stats" => _, + "thumbnail" => _, + "languages" => _, + "registrations" => _, + "poll_limits" => _, + "upload_limit" => _, + "avatar_upload_limit" => _, + "background_upload_limit" => _, + "banner_upload_limit" => _ + } = result + + assert email == from_config_email + end + + test "get instance stats", %{conn: conn} do + user = insert(:user, %{local: true}) + + user2 = insert(:user, %{local: true}) + {:ok, _user2} = User.deactivate(user2, !user2.deactivated) + + insert(:user, %{local: false, nickname: "u@peer1.com"}) + insert(:user, %{local: false, nickname: "u@peer2.com"}) + + {:ok, _} = Pleroma.Web.CommonAPI.post(user, %{"status" => "cofe"}) + + Pleroma.Stats.force_update() + + conn = get(conn, "/api/v1/instance") + + assert result = json_response(conn, 200) + + stats = result["stats"] + + assert stats + assert stats["user_count"] == 1 + assert stats["status_count"] == 1 + assert stats["domain_count"] == 2 + end + + test "get peers", %{conn: conn} do + insert(:user, %{local: false, nickname: "u@peer1.com"}) + insert(:user, %{local: false, nickname: "u@peer2.com"}) + + Pleroma.Stats.force_update() + + conn = get(conn, "/api/v1/instance/peers") + + assert result = json_response(conn, 200) + + assert ["peer1.com", "peer2.com"] == Enum.sort(result) + end +end diff --git a/test/web/mastodon_api/controllers/list_controller_test.exs b/test/web/mastodon_api/controllers/list_controller_test.exs index 093506309..c9c4cbb49 100644 --- a/test/web/mastodon_api/controllers/list_controller_test.exs +++ b/test/web/mastodon_api/controllers/list_controller_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.ListControllerTest do @@ -9,44 +9,35 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do import Pleroma.Factory - test "creating a list", %{conn: conn} do - user = insert(:user) + test "creating a list" do + %{conn: conn} = oauth_access(["write:lists"]) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/lists", %{"title" => "cuties"}) + conn = post(conn, "/api/v1/lists", %{"title" => "cuties"}) assert %{"title" => title} = json_response(conn, 200) assert title == "cuties" end - test "renders error for invalid params", %{conn: conn} do - user = insert(:user) + test "renders error for invalid params" do + %{conn: conn} = oauth_access(["write:lists"]) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/lists", %{"title" => nil}) + conn = post(conn, "/api/v1/lists", %{"title" => nil}) assert %{"error" => "can't be blank"} == json_response(conn, :unprocessable_entity) end - test "listing a user's lists", %{conn: conn} do - user = insert(:user) + test "listing a user's lists" do + %{conn: conn} = oauth_access(["read:lists", "write:lists"]) conn - |> assign(:user, user) |> post("/api/v1/lists", %{"title" => "cuties"}) + |> json_response(:ok) conn - |> assign(:user, user) |> post("/api/v1/lists", %{"title" => "cofe"}) + |> json_response(:ok) - conn = - conn - |> assign(:user, user) - |> get("/api/v1/lists") + conn = get(conn, "/api/v1/lists") assert [ %{"id" => _, "title" => "cofe"}, @@ -54,41 +45,35 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do ] = json_response(conn, :ok) end - test "adding users to a list", %{conn: conn} do - user = insert(:user) + test "adding users to a list" do + %{user: user, conn: conn} = oauth_access(["write:lists"]) other_user = insert(:user) {:ok, list} = Pleroma.List.create("name", user) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) + conn = post(conn, "/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) assert %{} == json_response(conn, 200) %Pleroma.List{following: following} = Pleroma.List.get(list.id, user) assert following == [other_user.follower_address] end - test "removing users from a list", %{conn: conn} do - user = insert(:user) + test "removing users from a list" do + %{user: user, conn: conn} = oauth_access(["write:lists"]) other_user = insert(:user) third_user = insert(:user) {:ok, list} = Pleroma.List.create("name", user) {:ok, list} = Pleroma.List.follow(list, other_user) {:ok, list} = Pleroma.List.follow(list, third_user) - conn = - conn - |> assign(:user, user) - |> delete("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) + conn = delete(conn, "/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) assert %{} == json_response(conn, 200) %Pleroma.List{following: following} = Pleroma.List.get(list.id, user) assert following == [third_user.follower_address] end - test "listing users in a list", %{conn: conn} do - user = insert(:user) + test "listing users in a list" do + %{user: user, conn: conn} = oauth_access(["read:lists"]) other_user = insert(:user) {:ok, list} = Pleroma.List.create("name", user) {:ok, list} = Pleroma.List.follow(list, other_user) @@ -102,8 +87,8 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do assert id == to_string(other_user.id) end - test "retrieving a list", %{conn: conn} do - user = insert(:user) + test "retrieving a list" do + %{user: user, conn: conn} = oauth_access(["read:lists"]) {:ok, list} = Pleroma.List.create("name", user) conn = @@ -115,32 +100,26 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do assert id == to_string(list.id) end - test "renders 404 if list is not found", %{conn: conn} do - user = insert(:user) + test "renders 404 if list is not found" do + %{conn: conn} = oauth_access(["read:lists"]) - conn = - conn - |> assign(:user, user) - |> get("/api/v1/lists/666") + conn = get(conn, "/api/v1/lists/666") assert %{"error" => "List not found"} = json_response(conn, :not_found) end - test "renaming a list", %{conn: conn} do - user = insert(:user) + test "renaming a list" do + %{user: user, conn: conn} = oauth_access(["write:lists"]) {:ok, list} = Pleroma.List.create("name", user) - conn = - conn - |> assign(:user, user) - |> put("/api/v1/lists/#{list.id}", %{"title" => "newname"}) + conn = put(conn, "/api/v1/lists/#{list.id}", %{"title" => "newname"}) assert %{"title" => name} = json_response(conn, 200) assert name == "newname" end - test "validates title when renaming a list", %{conn: conn} do - user = insert(:user) + test "validates title when renaming a list" do + %{user: user, conn: conn} = oauth_access(["write:lists"]) {:ok, list} = Pleroma.List.create("name", user) conn = @@ -151,14 +130,11 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do assert %{"error" => "can't be blank"} == json_response(conn, :unprocessable_entity) end - test "deleting a list", %{conn: conn} do - user = insert(:user) + test "deleting a list" do + %{user: user, conn: conn} = oauth_access(["write:lists"]) {:ok, list} = Pleroma.List.create("name", user) - conn = - conn - |> assign(:user, user) - |> delete("/api/v1/lists/#{list.id}") + conn = delete(conn, "/api/v1/lists/#{list.id}") assert %{} = json_response(conn, 200) assert is_nil(Repo.get(Pleroma.List, list.id)) diff --git a/test/web/mastodon_api/controllers/marker_controller_test.exs b/test/web/mastodon_api/controllers/marker_controller_test.exs new file mode 100644 index 000000000..919f295bd --- /dev/null +++ b/test/web/mastodon_api/controllers/marker_controller_test.exs @@ -0,0 +1,124 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do + use Pleroma.Web.ConnCase + + import Pleroma.Factory + + describe "GET /api/v1/markers" do + test "gets markers with correct scopes", %{conn: conn} do + user = insert(:user) + token = insert(:oauth_token, user: user, scopes: ["read:statuses"]) + + {:ok, %{"notifications" => marker}} = + Pleroma.Marker.upsert( + user, + %{"notifications" => %{"last_read_id" => "69420"}} + ) + + response = + conn + |> assign(:user, user) + |> assign(:token, token) + |> get("/api/v1/markers", %{timeline: ["notifications"]}) + |> json_response(200) + + assert response == %{ + "notifications" => %{ + "last_read_id" => "69420", + "updated_at" => NaiveDateTime.to_iso8601(marker.updated_at), + "version" => 0 + } + } + end + + test "gets markers with missed scopes", %{conn: conn} do + user = insert(:user) + token = insert(:oauth_token, user: user, scopes: []) + + Pleroma.Marker.upsert(user, %{"notifications" => %{"last_read_id" => "69420"}}) + + response = + conn + |> assign(:user, user) + |> assign(:token, token) + |> get("/api/v1/markers", %{timeline: ["notifications"]}) + |> json_response(403) + + assert response == %{"error" => "Insufficient permissions: read:statuses."} + end + end + + describe "POST /api/v1/markers" do + test "creates a marker with correct scopes", %{conn: conn} do + user = insert(:user) + token = insert(:oauth_token, user: user, scopes: ["write:statuses"]) + + response = + conn + |> assign(:user, user) + |> assign(:token, token) + |> post("/api/v1/markers", %{ + home: %{last_read_id: "777"}, + notifications: %{"last_read_id" => "69420"} + }) + |> json_response(200) + + assert %{ + "notifications" => %{ + "last_read_id" => "69420", + "updated_at" => _, + "version" => 0 + } + } = response + end + + test "updates exist marker", %{conn: conn} do + user = insert(:user) + token = insert(:oauth_token, user: user, scopes: ["write:statuses"]) + + {:ok, %{"notifications" => marker}} = + Pleroma.Marker.upsert( + user, + %{"notifications" => %{"last_read_id" => "69477"}} + ) + + response = + conn + |> assign(:user, user) + |> assign(:token, token) + |> post("/api/v1/markers", %{ + home: %{last_read_id: "777"}, + notifications: %{"last_read_id" => "69888"} + }) + |> json_response(200) + + assert response == %{ + "notifications" => %{ + "last_read_id" => "69888", + "updated_at" => NaiveDateTime.to_iso8601(marker.updated_at), + "version" => 0 + } + } + end + + test "creates a marker with missed scopes", %{conn: conn} do + user = insert(:user) + token = insert(:oauth_token, user: user, scopes: []) + + response = + conn + |> assign(:user, user) + |> assign(:token, token) + |> post("/api/v1/markers", %{ + home: %{last_read_id: "777"}, + notifications: %{"last_read_id" => "69420"} + }) + |> json_response(403) + + assert response == %{"error" => "Insufficient permissions: write:statuses."} + end + end +end diff --git a/test/web/mastodon_api/controllers/media_controller_test.exs b/test/web/mastodon_api/controllers/media_controller_test.exs new file mode 100644 index 000000000..203fa73b0 --- /dev/null +++ b/test/web/mastodon_api/controllers/media_controller_test.exs @@ -0,0 +1,82 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Object + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + + setup do: oauth_access(["write:media"]) + + describe "media upload" do + setup do + image = %Plug.Upload{ + content_type: "image/jpg", + path: Path.absname("test/fixtures/image.jpg"), + filename: "an_image.jpg" + } + + [image: image] + end + + clear_config([:media_proxy]) + clear_config([Pleroma.Upload]) + + test "returns uploaded image", %{conn: conn, image: image} do + desc = "Description of the image" + + media = + conn + |> post("/api/v1/media", %{"file" => image, "description" => desc}) + |> json_response(:ok) + + assert media["type"] == "image" + assert media["description"] == desc + assert media["id"] + + object = Object.get_by_id(media["id"]) + assert object.data["actor"] == User.ap_id(conn.assigns[:user]) + end + end + + describe "PUT /api/v1/media/:id" do + setup %{user: actor} do + file = %Plug.Upload{ + content_type: "image/jpg", + path: Path.absname("test/fixtures/image.jpg"), + filename: "an_image.jpg" + } + + {:ok, %Object{} = object} = + ActivityPub.upload( + file, + actor: User.ap_id(actor), + description: "test-m" + ) + + [object: object] + end + + test "updates name of media", %{conn: conn, object: object} do + media = + conn + |> put("/api/v1/media/#{object.id}", %{"description" => "test-media"}) + |> json_response(:ok) + + assert media["description"] == "test-media" + assert refresh_record(object).data["name"] == "test-media" + end + + test "returns error when request is bad", %{conn: conn, object: object} do + media = + conn + |> put("/api/v1/media/#{object.id}", %{}) + |> json_response(400) + + assert media == %{"error" => "bad_request"} + end + end +end diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs new file mode 100644 index 000000000..d452ddbdd --- /dev/null +++ b/test/web/mastodon_api/controllers/notification_controller_test.exs @@ -0,0 +1,490 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Notification + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + test "list of notifications" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + + {:ok, [_notification]} = Notification.create_notifications(activity) + + conn = + conn + |> assign(:user, user) + |> get("/api/v1/notifications") + + expected_response = + "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{ + user.ap_id + }\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>" + + assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200) + assert response == expected_response + end + + test "getting a single notification" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + + {:ok, [notification]} = Notification.create_notifications(activity) + + conn = get(conn, "/api/v1/notifications/#{notification.id}") + + expected_response = + "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{ + user.ap_id + }\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>" + + assert %{"status" => %{"content" => response}} = json_response(conn, 200) + assert response == expected_response + end + + test "dismissing a single notification" do + %{user: user, conn: conn} = oauth_access(["write:notifications"]) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + + {:ok, [notification]} = Notification.create_notifications(activity) + + conn = + conn + |> assign(:user, user) + |> post("/api/v1/notifications/dismiss", %{"id" => notification.id}) + + assert %{} = json_response(conn, 200) + end + + test "clearing all notifications" do + %{user: user, conn: conn} = oauth_access(["write:notifications", "read:notifications"]) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + + {:ok, [_notification]} = Notification.create_notifications(activity) + + ret_conn = post(conn, "/api/v1/notifications/clear") + + assert %{} = json_response(ret_conn, 200) + + ret_conn = get(conn, "/api/v1/notifications") + + assert all = json_response(ret_conn, 200) + assert all == [] + end + + test "paginates notifications using min_id, since_id, max_id, and limit" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) + other_user = insert(:user) + + {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + {:ok, activity3} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + {:ok, activity4} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + + notification1_id = get_notification_id_by_activity(activity1) + notification2_id = get_notification_id_by_activity(activity2) + notification3_id = get_notification_id_by_activity(activity3) + notification4_id = get_notification_id_by_activity(activity4) + + conn = assign(conn, :user, user) + + # min_id + result = + conn + |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}") + |> json_response(:ok) + + assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result + + # since_id + result = + conn + |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}") + |> json_response(:ok) + + assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result + + # max_id + result = + conn + |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}") + |> json_response(:ok) + + assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result + end + + describe "exclude_visibilities" do + test "filters notifications for mentions" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) + other_user = insert(:user) + + {:ok, public_activity} = + CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "public"}) + + {:ok, direct_activity} = + CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "direct"}) + + {:ok, unlisted_activity} = + CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "unlisted"}) + + {:ok, private_activity} = + CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "private"}) + + conn_res = + get(conn, "/api/v1/notifications", %{ + exclude_visibilities: ["public", "unlisted", "private"] + }) + + assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200) + assert id == direct_activity.id + + conn_res = + get(conn, "/api/v1/notifications", %{ + exclude_visibilities: ["public", "unlisted", "direct"] + }) + + assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200) + assert id == private_activity.id + + conn_res = + get(conn, "/api/v1/notifications", %{ + exclude_visibilities: ["public", "private", "direct"] + }) + + assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200) + assert id == unlisted_activity.id + + conn_res = + get(conn, "/api/v1/notifications", %{ + exclude_visibilities: ["unlisted", "private", "direct"] + }) + + assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200) + assert id == public_activity.id + end + + test "filters notifications for Like activities" do + user = insert(:user) + %{user: other_user, conn: conn} = oauth_access(["read:notifications"]) + + {:ok, public_activity} = + CommonAPI.post(other_user, %{"status" => ".", "visibility" => "public"}) + + {:ok, direct_activity} = + CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "direct"}) + + {:ok, unlisted_activity} = + CommonAPI.post(other_user, %{"status" => ".", "visibility" => "unlisted"}) + + {:ok, private_activity} = + CommonAPI.post(other_user, %{"status" => ".", "visibility" => "private"}) + + {:ok, _, _} = CommonAPI.favorite(public_activity.id, user) + {:ok, _, _} = CommonAPI.favorite(direct_activity.id, user) + {:ok, _, _} = CommonAPI.favorite(unlisted_activity.id, user) + {:ok, _, _} = CommonAPI.favorite(private_activity.id, user) + + activity_ids = + conn + |> get("/api/v1/notifications", %{exclude_visibilities: ["direct"]}) + |> json_response(200) + |> Enum.map(& &1["status"]["id"]) + + assert public_activity.id in activity_ids + assert unlisted_activity.id in activity_ids + assert private_activity.id in activity_ids + refute direct_activity.id in activity_ids + + activity_ids = + conn + |> get("/api/v1/notifications", %{exclude_visibilities: ["unlisted"]}) + |> json_response(200) + |> Enum.map(& &1["status"]["id"]) + + assert public_activity.id in activity_ids + refute unlisted_activity.id in activity_ids + assert private_activity.id in activity_ids + assert direct_activity.id in activity_ids + + activity_ids = + conn + |> get("/api/v1/notifications", %{exclude_visibilities: ["private"]}) + |> json_response(200) + |> Enum.map(& &1["status"]["id"]) + + assert public_activity.id in activity_ids + assert unlisted_activity.id in activity_ids + refute private_activity.id in activity_ids + assert direct_activity.id in activity_ids + + activity_ids = + conn + |> get("/api/v1/notifications", %{exclude_visibilities: ["public"]}) + |> json_response(200) + |> Enum.map(& &1["status"]["id"]) + + refute public_activity.id in activity_ids + assert unlisted_activity.id in activity_ids + assert private_activity.id in activity_ids + assert direct_activity.id in activity_ids + end + + test "filters notifications for Announce activities" do + user = insert(:user) + %{user: other_user, conn: conn} = oauth_access(["read:notifications"]) + + {:ok, public_activity} = + CommonAPI.post(other_user, %{"status" => ".", "visibility" => "public"}) + + {:ok, unlisted_activity} = + CommonAPI.post(other_user, %{"status" => ".", "visibility" => "unlisted"}) + + {:ok, _, _} = CommonAPI.repeat(public_activity.id, user) + {:ok, _, _} = CommonAPI.repeat(unlisted_activity.id, user) + + activity_ids = + conn + |> get("/api/v1/notifications", %{exclude_visibilities: ["unlisted"]}) + |> json_response(200) + |> Enum.map(& &1["status"]["id"]) + + assert public_activity.id in activity_ids + refute unlisted_activity.id in activity_ids + end + end + + test "filters notifications using exclude_types" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) + other_user = insert(:user) + + {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"}) + {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"}) + {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user) + {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user) + {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user) + + mention_notification_id = get_notification_id_by_activity(mention_activity) + favorite_notification_id = get_notification_id_by_activity(favorite_activity) + reblog_notification_id = get_notification_id_by_activity(reblog_activity) + follow_notification_id = get_notification_id_by_activity(follow_activity) + + conn_res = + get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]}) + + assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200) + + conn_res = + get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]}) + + assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200) + + conn_res = + get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]}) + + assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200) + + conn_res = + get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]}) + + assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200) + end + + test "destroy multiple" do + %{user: user, conn: conn} = oauth_access(["read:notifications", "write:notifications"]) + other_user = insert(:user) + + {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + {:ok, activity3} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"}) + {:ok, activity4} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"}) + + notification1_id = get_notification_id_by_activity(activity1) + notification2_id = get_notification_id_by_activity(activity2) + notification3_id = get_notification_id_by_activity(activity3) + notification4_id = get_notification_id_by_activity(activity4) + + result = + conn + |> get("/api/v1/notifications") + |> json_response(:ok) + + assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result + + conn2 = + conn + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:notifications"])) + + result = + conn2 + |> get("/api/v1/notifications") + |> json_response(:ok) + + assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result + + conn_destroy = + conn + |> delete("/api/v1/notifications/destroy_multiple", %{ + "ids" => [notification1_id, notification2_id] + }) + + assert json_response(conn_destroy, 200) == %{} + + result = + conn2 + |> get("/api/v1/notifications") + |> json_response(:ok) + + assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result + end + + test "doesn't see notifications after muting user with notifications" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) + user2 = insert(:user) + + {:ok, _, _, _} = CommonAPI.follow(user, user2) + {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"}) + + ret_conn = get(conn, "/api/v1/notifications") + + assert length(json_response(ret_conn, 200)) == 1 + + {:ok, _user_relationships} = User.mute(user, user2) + + conn = get(conn, "/api/v1/notifications") + + assert json_response(conn, 200) == [] + end + + test "see notifications after muting user without notifications" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) + user2 = insert(:user) + + {:ok, _, _, _} = CommonAPI.follow(user, user2) + {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"}) + + ret_conn = get(conn, "/api/v1/notifications") + + assert length(json_response(ret_conn, 200)) == 1 + + {:ok, _user_relationships} = User.mute(user, user2, false) + + conn = get(conn, "/api/v1/notifications") + + assert length(json_response(conn, 200)) == 1 + end + + test "see notifications after muting user with notifications and with_muted parameter" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) + user2 = insert(:user) + + {:ok, _, _, _} = CommonAPI.follow(user, user2) + {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"}) + + ret_conn = get(conn, "/api/v1/notifications") + + assert length(json_response(ret_conn, 200)) == 1 + + {:ok, _user_relationships} = User.mute(user, user2) + + conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"}) + + assert length(json_response(conn, 200)) == 1 + end + + test "see move notifications with `with_move` parameter" do + old_user = insert(:user) + new_user = insert(:user, also_known_as: [old_user.ap_id]) + %{user: follower, conn: conn} = oauth_access(["read:notifications"]) + + User.follow(follower, old_user) + Pleroma.Web.ActivityPub.ActivityPub.move(old_user, new_user) + Pleroma.Tests.ObanHelpers.perform_all() + + ret_conn = get(conn, "/api/v1/notifications") + + assert json_response(ret_conn, 200) == [] + + conn = get(conn, "/api/v1/notifications", %{"with_move" => "true"}) + + assert length(json_response(conn, 200)) == 1 + end + + describe "link headers" do + test "preserves parameters in link headers" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) + other_user = insert(:user) + + {:ok, activity1} = + CommonAPI.post(other_user, %{ + "status" => "hi @#{user.nickname}", + "visibility" => "public" + }) + + {:ok, activity2} = + CommonAPI.post(other_user, %{ + "status" => "hi @#{user.nickname}", + "visibility" => "public" + }) + + notification1 = Repo.get_by(Notification, activity_id: activity1.id) + notification2 = Repo.get_by(Notification, activity_id: activity2.id) + + conn = + conn + |> assign(:user, user) + |> get("/api/v1/notifications", %{media_only: true}) + + assert [link_header] = get_resp_header(conn, "link") + assert link_header =~ ~r/media_only=true/ + assert link_header =~ ~r/min_id=#{notification2.id}/ + assert link_header =~ ~r/max_id=#{notification1.id}/ + end + end + + describe "from specified user" do + test "account_id" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) + + %{id: account_id} = other_user1 = insert(:user) + other_user2 = insert(:user) + + {:ok, _activity} = CommonAPI.post(other_user1, %{"status" => "hi @#{user.nickname}"}) + {:ok, _activity} = CommonAPI.post(other_user2, %{"status" => "bye @#{user.nickname}"}) + + assert [%{"account" => %{"id" => ^account_id}}] = + conn + |> assign(:user, user) + |> get("/api/v1/notifications", %{account_id: account_id}) + |> json_response(200) + + assert %{"error" => "Account is not found"} = + conn + |> assign(:user, user) + |> get("/api/v1/notifications", %{account_id: "cofe"}) + |> json_response(404) + end + end + + defp get_notification_id_by_activity(%{id: id}) do + Notification + |> Repo.get_by(activity_id: id) + |> Map.get(:id) + |> to_string() + end +end diff --git a/test/web/mastodon_api/controllers/poll_controller_test.exs b/test/web/mastodon_api/controllers/poll_controller_test.exs new file mode 100644 index 000000000..88b13a25a --- /dev/null +++ b/test/web/mastodon_api/controllers/poll_controller_test.exs @@ -0,0 +1,157 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.PollControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Object + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + describe "GET /api/v1/polls/:id" do + setup do: oauth_access(["read:statuses"]) + + test "returns poll entity for object id", %{user: user, conn: conn} do + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => "Pleroma does", + "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20} + }) + + object = Object.normalize(activity) + + conn = get(conn, "/api/v1/polls/#{object.id}") + + response = json_response(conn, 200) + id = to_string(object.id) + assert %{"id" => ^id, "expired" => false, "multiple" => false} = response + end + + test "does not expose polls for private statuses", %{conn: conn} do + other_user = insert(:user) + + {:ok, activity} = + CommonAPI.post(other_user, %{ + "status" => "Pleroma does", + "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20}, + "visibility" => "private" + }) + + object = Object.normalize(activity) + + conn = get(conn, "/api/v1/polls/#{object.id}") + + assert json_response(conn, 404) + end + end + + describe "POST /api/v1/polls/:id/votes" do + setup do: oauth_access(["write:statuses"]) + + test "votes are added to the poll", %{conn: conn} do + other_user = insert(:user) + + {:ok, activity} = + CommonAPI.post(other_user, %{ + "status" => "A very delicious sandwich", + "poll" => %{ + "options" => ["Lettuce", "Grilled Bacon", "Tomato"], + "expires_in" => 20, + "multiple" => true + } + }) + + object = Object.normalize(activity) + + conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]}) + + assert json_response(conn, 200) + object = Object.get_by_id(object.id) + + assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} -> + total_items == 1 + end) + end + + test "author can't vote", %{user: user, conn: conn} do + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => "Am I cute?", + "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20} + }) + + object = Object.normalize(activity) + + assert conn + |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]}) + |> json_response(422) == %{"error" => "Poll's author can't vote"} + + object = Object.get_by_id(object.id) + + refute Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 1 + end + + test "does not allow multiple choices on a single-choice question", %{conn: conn} do + other_user = insert(:user) + + {:ok, activity} = + CommonAPI.post(other_user, %{ + "status" => "The glass is", + "poll" => %{"options" => ["half empty", "half full"], "expires_in" => 20} + }) + + object = Object.normalize(activity) + + assert conn + |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]}) + |> json_response(422) == %{"error" => "Too many choices"} + + object = Object.get_by_id(object.id) + + refute Enum.any?(object.data["oneOf"], fn %{"replies" => %{"totalItems" => total_items}} -> + total_items == 1 + end) + end + + test "does not allow choice index to be greater than options count", %{conn: conn} do + other_user = insert(:user) + + {:ok, activity} = + CommonAPI.post(other_user, %{ + "status" => "Am I cute?", + "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20} + }) + + object = Object.normalize(activity) + + conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [2]}) + + assert json_response(conn, 422) == %{"error" => "Invalid indices"} + end + + test "returns 404 error when object is not exist", %{conn: conn} do + conn = post(conn, "/api/v1/polls/1/votes", %{"choices" => [0]}) + + assert json_response(conn, 404) == %{"error" => "Record not found"} + end + + test "returns 404 when poll is private and not available for user", %{conn: conn} do + other_user = insert(:user) + + {:ok, activity} = + CommonAPI.post(other_user, %{ + "status" => "Am I cute?", + "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}, + "visibility" => "private" + }) + + object = Object.normalize(activity) + + conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [0]}) + + assert json_response(conn, 404) == %{"error" => "Record not found"} + end + end +end diff --git a/test/web/mastodon_api/controllers/report_controller_test.exs b/test/web/mastodon_api/controllers/report_controller_test.exs new file mode 100644 index 000000000..34ec8119e --- /dev/null +++ b/test/web/mastodon_api/controllers/report_controller_test.exs @@ -0,0 +1,87 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.ReportControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + setup do: oauth_access(["write:reports"]) + + setup do + target_user = insert(:user) + + {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"}) + + [target_user: target_user, activity: activity] + end + + test "submit a basic report", %{conn: conn, target_user: target_user} do + assert %{"action_taken" => false, "id" => _} = + conn + |> post("/api/v1/reports", %{"account_id" => target_user.id}) + |> json_response(200) + end + + test "submit a report with statuses and comment", %{ + conn: conn, + target_user: target_user, + activity: activity + } do + assert %{"action_taken" => false, "id" => _} = + conn + |> post("/api/v1/reports", %{ + "account_id" => target_user.id, + "status_ids" => [activity.id], + "comment" => "bad status!", + "forward" => "false" + }) + |> json_response(200) + end + + test "account_id is required", %{ + conn: conn, + activity: activity + } do + assert %{"error" => "Valid `account_id` required"} = + conn + |> post("/api/v1/reports", %{"status_ids" => [activity.id]}) + |> json_response(400) + end + + test "comment must be up to the size specified in the config", %{ + conn: conn, + target_user: target_user + } do + max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000) + comment = String.pad_trailing("a", max_size + 1, "a") + + error = %{"error" => "Comment must be up to #{max_size} characters"} + + assert ^error = + conn + |> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment}) + |> json_response(400) + end + + test "returns error when account is not exist", %{ + conn: conn, + activity: activity + } do + conn = post(conn, "/api/v1/reports", %{"status_ids" => [activity.id], "account_id" => "foo"}) + + assert json_response(conn, 400) == %{"error" => "Account not found"} + end + + test "doesn't fail if an admin has no email", %{conn: conn, target_user: target_user} do + insert(:user, %{is_admin: true, email: nil}) + + assert %{"action_taken" => false, "id" => _} = + conn + |> post("/api/v1/reports", %{"account_id" => target_user.id}) + |> json_response(200) + end +end diff --git a/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs b/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs new file mode 100644 index 000000000..3cd08c189 --- /dev/null +++ b/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs @@ -0,0 +1,129 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Repo + alias Pleroma.ScheduledActivity + + import Pleroma.Factory + import Ecto.Query + + clear_config([ScheduledActivity, :enabled]) + + test "shows scheduled activities" do + %{user: user, conn: conn} = oauth_access(["read:statuses"]) + + scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string() + scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string() + scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string() + scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string() + + # min_id + conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}") + + result = json_response(conn_res, 200) + assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result + + # since_id + conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}") + + result = json_response(conn_res, 200) + assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result + + # max_id + conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}") + + result = json_response(conn_res, 200) + assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result + end + + test "shows a scheduled activity" do + %{user: user, conn: conn} = oauth_access(["read:statuses"]) + scheduled_activity = insert(:scheduled_activity, user: user) + + res_conn = get(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}") + + assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200) + assert scheduled_activity_id == scheduled_activity.id |> to_string() + + res_conn = get(conn, "/api/v1/scheduled_statuses/404") + + assert %{"error" => "Record not found"} = json_response(res_conn, 404) + end + + test "updates a scheduled activity" do + Pleroma.Config.put([ScheduledActivity, :enabled], true) + %{user: user, conn: conn} = oauth_access(["write:statuses"]) + + scheduled_at = Timex.shift(NaiveDateTime.utc_now(), minutes: 60) + + {:ok, scheduled_activity} = + ScheduledActivity.create( + user, + %{ + scheduled_at: scheduled_at, + params: build(:note).data + } + ) + + job = Repo.one(from(j in Oban.Job, where: j.queue == "scheduled_activities")) + + assert job.args == %{"activity_id" => scheduled_activity.id} + assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(scheduled_at) + + new_scheduled_at = Timex.shift(NaiveDateTime.utc_now(), minutes: 120) + + res_conn = + put(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{ + scheduled_at: new_scheduled_at + }) + + assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200) + assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at) + job = refresh_record(job) + + assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(new_scheduled_at) + + res_conn = put(conn, "/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at}) + + assert %{"error" => "Record not found"} = json_response(res_conn, 404) + end + + test "deletes a scheduled activity" do + Pleroma.Config.put([ScheduledActivity, :enabled], true) + %{user: user, conn: conn} = oauth_access(["write:statuses"]) + scheduled_at = Timex.shift(NaiveDateTime.utc_now(), minutes: 60) + + {:ok, scheduled_activity} = + ScheduledActivity.create( + user, + %{ + scheduled_at: scheduled_at, + params: build(:note).data + } + ) + + job = Repo.one(from(j in Oban.Job, where: j.queue == "scheduled_activities")) + + assert job.args == %{"activity_id" => scheduled_activity.id} + + res_conn = + conn + |> assign(:user, user) + |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") + + assert %{} = json_response(res_conn, 200) + refute Repo.get(ScheduledActivity, scheduled_activity.id) + refute Repo.get(Oban.Job, job.id) + + res_conn = + conn + |> assign(:user, user) + |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") + + assert %{"error" => "Record not found"} = json_response(res_conn, 404) + end +end diff --git a/test/web/mastodon_api/controllers/search_controller_test.exs b/test/web/mastodon_api/controllers/search_controller_test.exs index 49c79ff0a..11133ff66 100644 --- a/test/web/mastodon_api/controllers/search_controller_test.exs +++ b/test/web/mastodon_api/controllers/search_controller_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do @@ -42,7 +42,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do user_two = insert(:user, %{nickname: "shp@shitposter.club"}) user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"}) - {:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu private"}) + {:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu private 天子"}) {:ok, _activity} = CommonAPI.post(user, %{ @@ -52,9 +52,10 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do {:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"}) - conn = get(conn, "/api/v2/search", %{"q" => "2hu #private"}) - - assert results = json_response(conn, 200) + results = + conn + |> get("/api/v2/search", %{"q" => "2hu #private"}) + |> json_response(200) [account | _] = results["accounts"] assert account["id"] == to_string(user_three.id) @@ -65,18 +66,47 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do [status] = results["statuses"] assert status["id"] == to_string(activity.id) + + results = + get(conn, "/api/v2/search", %{"q" => "天子"}) + |> json_response(200) + + [status] = results["statuses"] + assert status["id"] == to_string(activity.id) + end + + test "excludes a blocked users from search results", %{conn: conn} do + user = insert(:user) + user_smith = insert(:user, %{nickname: "Agent", name: "I love 2hu"}) + user_neo = insert(:user, %{nickname: "Agent Neo", name: "Agent"}) + + {:ok, act1} = CommonAPI.post(user, %{"status" => "This is about 2hu private 天子"}) + {:ok, act2} = CommonAPI.post(user_smith, %{"status" => "Agent Smith"}) + {:ok, act3} = CommonAPI.post(user_neo, %{"status" => "Agent Smith"}) + Pleroma.User.block(user, user_smith) + + results = + conn + |> assign(:user, user) + |> assign(:token, insert(:oauth_token, user: user, scopes: ["read"])) + |> get("/api/v2/search", %{"q" => "Agent"}) + |> json_response(200) + + status_ids = Enum.map(results["statuses"], fn g -> g["id"] end) + + assert act3.id in status_ids + refute act2.id in status_ids + refute act1.id in status_ids end end describe ".account_search" do test "account search", %{conn: conn} do - user = insert(:user) user_two = insert(:user, %{nickname: "shp@shitposter.club"}) user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"}) results = conn - |> assign(:user, user) |> get("/api/v1/accounts/search", %{"q" => "shp"}) |> json_response(200) @@ -87,7 +117,6 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do results = conn - |> assign(:user, user) |> get("/api/v1/accounts/search", %{"q" => "2hu"}) |> json_response(200) @@ -97,11 +126,10 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do end test "returns account if query contains a space", %{conn: conn} do - user = insert(:user, %{nickname: "shp@shitposter.club"}) + insert(:user, %{nickname: "shp@shitposter.club"}) results = conn - |> assign(:user, user) |> get("/api/v1/accounts/search", %{"q" => "shp@shitposter.club xxx "}) |> json_response(200) @@ -143,11 +171,10 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do {:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"}) - conn = + results = conn |> get("/api/v1/search", %{"q" => "2hu"}) - - assert results = json_response(conn, 200) + |> json_response(200) [account | _] = results["accounts"] assert account["id"] == to_string(user_three.id) @@ -158,15 +185,19 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do assert status["id"] == to_string(activity.id) end - test "search fetches remote statuses", %{conn: conn} do + test "search fetches remote statuses and prefers them over other results", %{conn: conn} do capture_log(fn -> - conn = + {:ok, %{id: activity_id}} = + CommonAPI.post(insert(:user), %{ + "status" => "check out https://shitposter.club/notice/2827873" + }) + + results = conn |> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"}) + |> json_response(200) - assert results = json_response(conn, 200) - - [status] = results["statuses"] + [status, %{"id" => ^activity_id}] = results["statuses"] assert status["uri"] == "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment" @@ -181,11 +212,10 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do }) capture_log(fn -> - conn = + results = conn |> get("/api/v1/search", %{"q" => Object.normalize(activity).data["id"]}) - - assert results = json_response(conn, 200) + |> json_response(200) [] = results["statuses"] end) @@ -194,22 +224,23 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do test "search fetches remote accounts", %{conn: conn} do user = insert(:user) - conn = + results = conn |> assign(:user, user) - |> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "true"}) + |> assign(:token, insert(:oauth_token, user: user, scopes: ["read"])) + |> get("/api/v1/search", %{"q" => "mike@osada.macgirvin.com", "resolve" => "true"}) + |> json_response(200) - assert results = json_response(conn, 200) [account] = results["accounts"] - assert account["acct"] == "shp@social.heldscal.la" + assert account["acct"] == "mike@osada.macgirvin.com" end test "search doesn't fetch remote accounts if resolve is false", %{conn: conn} do - conn = + results = conn - |> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "false"}) + |> get("/api/v1/search", %{"q" => "mike@osada.macgirvin.com", "resolve" => "false"}) + |> json_response(200) - assert results = json_response(conn, 200) assert [] == results["accounts"] end diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs new file mode 100644 index 000000000..fbf63f608 --- /dev/null +++ b/test/web/mastodon_api/controllers/status_controller_test.exs @@ -0,0 +1,1300 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Activity + alias Pleroma.ActivityExpiration + alias Pleroma.Config + alias Pleroma.Conversation.Participation + alias Pleroma.Object + alias Pleroma.Repo + alias Pleroma.ScheduledActivity + alias Pleroma.Tests.ObanHelpers + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + clear_config([:instance, :federating]) + clear_config([:instance, :allow_relay]) + clear_config([:rich_media, :enabled]) + + describe "posting statuses" do + setup do: oauth_access(["write:statuses"]) + + test "posting a status does not increment reblog_count when relaying", %{conn: conn} do + Pleroma.Config.put([:instance, :federating], true) + Pleroma.Config.get([:instance, :allow_relay], true) + + response = + conn + |> post("api/v1/statuses", %{ + "content_type" => "text/plain", + "source" => "Pleroma FE", + "status" => "Hello world", + "visibility" => "public" + }) + |> json_response(200) + + assert response["reblogs_count"] == 0 + ObanHelpers.perform_all() + + response = + conn + |> get("api/v1/statuses/#{response["id"]}", %{}) + |> json_response(200) + + assert response["reblogs_count"] == 0 + end + + test "posting a status", %{conn: conn} do + idempotency_key = "Pikachu rocks!" + + conn_one = + conn + |> put_req_header("idempotency-key", idempotency_key) + |> post("/api/v1/statuses", %{ + "status" => "cofe", + "spoiler_text" => "2hu", + "sensitive" => "false" + }) + + {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key) + # Six hours + assert ttl > :timer.seconds(6 * 60 * 60 - 1) + + assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} = + json_response(conn_one, 200) + + assert Activity.get_by_id(id) + + conn_two = + conn + |> put_req_header("idempotency-key", idempotency_key) + |> post("/api/v1/statuses", %{ + "status" => "cofe", + "spoiler_text" => "2hu", + "sensitive" => "false" + }) + + assert %{"id" => second_id} = json_response(conn_two, 200) + assert id == second_id + + conn_three = + conn + |> post("/api/v1/statuses", %{ + "status" => "cofe", + "spoiler_text" => "2hu", + "sensitive" => "false" + }) + + assert %{"id" => third_id} = json_response(conn_three, 200) + refute id == third_id + + # An activity that will expire: + # 2 hours + expires_in = 120 * 60 + + conn_four = + conn + |> post("api/v1/statuses", %{ + "status" => "oolong", + "expires_in" => expires_in + }) + + assert fourth_response = %{"id" => fourth_id} = json_response(conn_four, 200) + assert activity = Activity.get_by_id(fourth_id) + assert expiration = ActivityExpiration.get_by_activity_id(fourth_id) + + estimated_expires_at = + NaiveDateTime.utc_now() + |> NaiveDateTime.add(expires_in) + |> NaiveDateTime.truncate(:second) + + # This assert will fail if the test takes longer than a minute. I sure hope it never does: + assert abs(NaiveDateTime.diff(expiration.scheduled_at, estimated_expires_at, :second)) < 60 + + assert fourth_response["pleroma"]["expires_at"] == + NaiveDateTime.to_iso8601(expiration.scheduled_at) + end + + test "it fails to create a status if `expires_in` is less or equal than an hour", %{ + conn: conn + } do + # 1 hour + expires_in = 60 * 60 + + assert %{"error" => "Expiry date is too soon"} = + conn + |> post("api/v1/statuses", %{ + "status" => "oolong", + "expires_in" => expires_in + }) + |> json_response(422) + + # 30 minutes + expires_in = 30 * 60 + + assert %{"error" => "Expiry date is too soon"} = + conn + |> post("api/v1/statuses", %{ + "status" => "oolong", + "expires_in" => expires_in + }) + |> json_response(422) + end + + test "posting an undefined status with an attachment", %{user: user, conn: conn} do + file = %Plug.Upload{ + content_type: "image/jpg", + path: Path.absname("test/fixtures/image.jpg"), + filename: "an_image.jpg" + } + + {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id) + + conn = + post(conn, "/api/v1/statuses", %{ + "media_ids" => [to_string(upload.id)] + }) + + assert json_response(conn, 200) + end + + test "replying to a status", %{user: user, conn: conn} do + {:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"}) + + conn = + conn + |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id}) + + assert %{"content" => "xD", "id" => id} = json_response(conn, 200) + + activity = Activity.get_by_id(id) + + assert activity.data["context"] == replied_to.data["context"] + assert Activity.get_in_reply_to_activity(activity).id == replied_to.id + end + + test "replying to a direct message with visibility other than direct", %{ + user: user, + conn: conn + } do + {:ok, replied_to} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"}) + + Enum.each(["public", "private", "unlisted"], fn visibility -> + conn = + conn + |> post("/api/v1/statuses", %{ + "status" => "@#{user.nickname} hey", + "in_reply_to_id" => replied_to.id, + "visibility" => visibility + }) + + assert json_response(conn, 422) == %{"error" => "The message visibility must be direct"} + end) + end + + test "posting a status with an invalid in_reply_to_id", %{conn: conn} do + conn = post(conn, "/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""}) + + assert %{"content" => "xD", "id" => id} = json_response(conn, 200) + assert Activity.get_by_id(id) + end + + test "posting a sensitive status", %{conn: conn} do + conn = post(conn, "/api/v1/statuses", %{"status" => "cofe", "sensitive" => true}) + + assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200) + assert Activity.get_by_id(id) + end + + test "posting a fake status", %{conn: conn} do + real_conn = + post(conn, "/api/v1/statuses", %{ + "status" => + "\"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" + }) + + real_status = json_response(real_conn, 200) + + assert real_status + assert Object.get_by_ap_id(real_status["uri"]) + + real_status = + real_status + |> Map.put("id", nil) + |> Map.put("url", nil) + |> Map.put("uri", nil) + |> Map.put("created_at", nil) + |> Kernel.put_in(["pleroma", "conversation_id"], nil) + + fake_conn = + post(conn, "/api/v1/statuses", %{ + "status" => + "\"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", + "preview" => true + }) + + fake_status = json_response(fake_conn, 200) + + assert fake_status + refute Object.get_by_ap_id(fake_status["uri"]) + + fake_status = + fake_status + |> Map.put("id", nil) + |> Map.put("url", nil) + |> Map.put("uri", nil) + |> Map.put("created_at", nil) + |> Kernel.put_in(["pleroma", "conversation_id"], nil) + + assert real_status == fake_status + end + + test "posting a status with OGP link preview", %{conn: conn} do + Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + Config.put([:rich_media, :enabled], true) + + conn = + post(conn, "/api/v1/statuses", %{ + "status" => "https://example.com/ogp" + }) + + assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200) + assert Activity.get_by_id(id) + end + + test "posting a direct status", %{conn: conn} do + user2 = insert(:user) + content = "direct cofe @#{user2.nickname}" + + conn = post(conn, "api/v1/statuses", %{"status" => content, "visibility" => "direct"}) + + assert %{"id" => id} = response = json_response(conn, 200) + assert response["visibility"] == "direct" + assert response["pleroma"]["direct_conversation_id"] + assert activity = Activity.get_by_id(id) + assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id] + assert activity.data["to"] == [user2.ap_id] + assert activity.data["cc"] == [] + end + end + + describe "posting scheduled statuses" do + setup do: oauth_access(["write:statuses"]) + + test "creates a scheduled activity", %{conn: conn} do + scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) + + conn = + post(conn, "/api/v1/statuses", %{ + "status" => "scheduled", + "scheduled_at" => scheduled_at + }) + + assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200) + assert expected_scheduled_at == CommonAPI.Utils.to_masto_date(scheduled_at) + assert [] == Repo.all(Activity) + end + + test "creates a scheduled activity with a media attachment", %{user: user, conn: conn} do + scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) + + file = %Plug.Upload{ + content_type: "image/jpg", + path: Path.absname("test/fixtures/image.jpg"), + filename: "an_image.jpg" + } + + {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id) + + conn = + post(conn, "/api/v1/statuses", %{ + "media_ids" => [to_string(upload.id)], + "status" => "scheduled", + "scheduled_at" => scheduled_at + }) + + assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200) + assert %{"type" => "image"} = media_attachment + end + + test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now", + %{conn: conn} do + scheduled_at = + NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond) + + conn = + post(conn, "/api/v1/statuses", %{ + "status" => "not scheduled", + "scheduled_at" => scheduled_at + }) + + assert %{"content" => "not scheduled"} = json_response(conn, 200) + assert [] == Repo.all(ScheduledActivity) + end + + test "returns error when daily user limit is exceeded", %{user: user, conn: conn} do + today = + NaiveDateTime.utc_now() + |> NaiveDateTime.add(:timer.minutes(6), :millisecond) + |> NaiveDateTime.to_iso8601() + + attrs = %{params: %{}, scheduled_at: today} + {:ok, _} = ScheduledActivity.create(user, attrs) + {:ok, _} = ScheduledActivity.create(user, attrs) + + conn = post(conn, "/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today}) + + assert %{"error" => "daily limit exceeded"} == json_response(conn, 422) + end + + test "returns error when total user limit is exceeded", %{user: user, conn: conn} do + today = + NaiveDateTime.utc_now() + |> NaiveDateTime.add(:timer.minutes(6), :millisecond) + |> NaiveDateTime.to_iso8601() + + tomorrow = + NaiveDateTime.utc_now() + |> NaiveDateTime.add(:timer.hours(36), :millisecond) + |> NaiveDateTime.to_iso8601() + + attrs = %{params: %{}, scheduled_at: today} + {:ok, _} = ScheduledActivity.create(user, attrs) + {:ok, _} = ScheduledActivity.create(user, attrs) + {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow}) + + conn = + post(conn, "/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow}) + + assert %{"error" => "total limit exceeded"} == json_response(conn, 422) + end + end + + describe "posting polls" do + setup do: oauth_access(["write:statuses"]) + + test "posting a poll", %{conn: conn} do + time = NaiveDateTime.utc_now() + + conn = + post(conn, "/api/v1/statuses", %{ + "status" => "Who is the #bestgrill?", + "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420} + }) + + response = json_response(conn, 200) + + assert Enum.all?(response["poll"]["options"], fn %{"title" => title} -> + title in ["Rei", "Asuka", "Misato"] + end) + + assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430 + refute response["poll"]["expred"] + + question = Object.get_by_id(response["poll"]["id"]) + + # closed contains utc timezone + assert question.data["closed"] =~ "Z" + end + + test "option limit is enforced", %{conn: conn} do + limit = Config.get([:instance, :poll_limits, :max_options]) + + conn = + post(conn, "/api/v1/statuses", %{ + "status" => "desu~", + "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1} + }) + + %{"error" => error} = json_response(conn, 422) + assert error == "Poll can't contain more than #{limit} options" + end + + test "option character limit is enforced", %{conn: conn} do + limit = Config.get([:instance, :poll_limits, :max_option_chars]) + + conn = + post(conn, "/api/v1/statuses", %{ + "status" => "...", + "poll" => %{ + "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)], + "expires_in" => 1 + } + }) + + %{"error" => error} = json_response(conn, 422) + assert error == "Poll options cannot be longer than #{limit} characters each" + end + + test "minimal date limit is enforced", %{conn: conn} do + limit = Config.get([:instance, :poll_limits, :min_expiration]) + + conn = + post(conn, "/api/v1/statuses", %{ + "status" => "imagine arbitrary limits", + "poll" => %{ + "options" => ["this post was made by pleroma gang"], + "expires_in" => limit - 1 + } + }) + + %{"error" => error} = json_response(conn, 422) + assert error == "Expiration date is too soon" + end + + test "maximum date limit is enforced", %{conn: conn} do + limit = Config.get([:instance, :poll_limits, :max_expiration]) + + conn = + post(conn, "/api/v1/statuses", %{ + "status" => "imagine arbitrary limits", + "poll" => %{ + "options" => ["this post was made by pleroma gang"], + "expires_in" => limit + 1 + } + }) + + %{"error" => error} = json_response(conn, 422) + assert error == "Expiration date is too far in the future" + end + end + + test "get a status" do + %{conn: conn} = oauth_access(["read:statuses"]) + activity = insert(:note_activity) + + conn = get(conn, "/api/v1/statuses/#{activity.id}") + + assert %{"id" => id} = json_response(conn, 200) + assert id == to_string(activity.id) + end + + test "getting a status that doesn't exist returns 404" do + %{conn: conn} = oauth_access(["read:statuses"]) + activity = insert(:note_activity) + + conn = get(conn, "/api/v1/statuses/#{String.downcase(activity.id)}") + + assert json_response(conn, 404) == %{"error" => "Record not found"} + end + + test "get a direct status" do + %{user: user, conn: conn} = oauth_access(["read:statuses"]) + other_user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{"status" => "@#{other_user.nickname}", "visibility" => "direct"}) + + conn = + conn + |> assign(:user, user) + |> get("/api/v1/statuses/#{activity.id}") + + [participation] = Participation.for_user(user) + + res = json_response(conn, 200) + assert res["pleroma"]["direct_conversation_id"] == participation.id + end + + test "get statuses by IDs" do + %{conn: conn} = oauth_access(["read:statuses"]) + %{id: id1} = insert(:note_activity) + %{id: id2} = insert(:note_activity) + + query_string = "ids[]=#{id1}&ids[]=#{id2}" + conn = get(conn, "/api/v1/statuses/?#{query_string}") + + assert [%{"id" => ^id1}, %{"id" => ^id2}] = Enum.sort_by(json_response(conn, :ok), & &1["id"]) + end + + describe "deleting a status" do + test "when you created it" do + %{user: author, conn: conn} = oauth_access(["write:statuses"]) + activity = insert(:note_activity, user: author) + + conn = + conn + |> assign(:user, author) + |> delete("/api/v1/statuses/#{activity.id}") + + assert %{} = json_response(conn, 200) + + refute Activity.get_by_id(activity.id) + end + + test "when it doesn't exist" do + %{user: author, conn: conn} = oauth_access(["write:statuses"]) + activity = insert(:note_activity, user: author) + + conn = + conn + |> assign(:user, author) + |> delete("/api/v1/statuses/#{String.downcase(activity.id)}") + + assert %{"error" => "Record not found"} == json_response(conn, 404) + end + + test "when you didn't create it" do + %{conn: conn} = oauth_access(["write:statuses"]) + activity = insert(:note_activity) + + conn = delete(conn, "/api/v1/statuses/#{activity.id}") + + assert %{"error" => _} = json_response(conn, 403) + + assert Activity.get_by_id(activity.id) == activity + end + + test "when you're an admin or moderator", %{conn: conn} do + activity1 = insert(:note_activity) + activity2 = insert(:note_activity) + admin = insert(:user, is_admin: true) + moderator = insert(:user, is_moderator: true) + + res_conn = + conn + |> assign(:user, admin) + |> assign(:token, insert(:oauth_token, user: admin, scopes: ["write:statuses"])) + |> delete("/api/v1/statuses/#{activity1.id}") + + assert %{} = json_response(res_conn, 200) + + res_conn = + conn + |> assign(:user, moderator) + |> assign(:token, insert(:oauth_token, user: moderator, scopes: ["write:statuses"])) + |> delete("/api/v1/statuses/#{activity2.id}") + + assert %{} = json_response(res_conn, 200) + + refute Activity.get_by_id(activity1.id) + refute Activity.get_by_id(activity2.id) + end + end + + describe "reblogging" do + setup do: oauth_access(["write:statuses"]) + + test "reblogs and returns the reblogged status", %{conn: conn} do + activity = insert(:note_activity) + + conn = post(conn, "/api/v1/statuses/#{activity.id}/reblog") + + assert %{ + "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}, + "reblogged" => true + } = json_response(conn, 200) + + assert to_string(activity.id) == id + end + + test "returns 404 if the reblogged status doesn't exist", %{conn: conn} do + activity = insert(:note_activity) + + conn = post(conn, "/api/v1/statuses/#{String.downcase(activity.id)}/reblog") + + assert %{"error" => "Record not found"} = json_response(conn, 404) + end + + test "reblogs privately and returns the reblogged status", %{conn: conn} do + activity = insert(:note_activity) + + conn = post(conn, "/api/v1/statuses/#{activity.id}/reblog", %{"visibility" => "private"}) + + assert %{ + "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}, + "reblogged" => true, + "visibility" => "private" + } = json_response(conn, 200) + + assert to_string(activity.id) == id + end + + test "reblogged status for another user" do + activity = insert(:note_activity) + user1 = insert(:user) + user2 = insert(:user) + user3 = insert(:user) + CommonAPI.favorite(activity.id, user2) + {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id) + {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1) + {:ok, _, _object} = CommonAPI.repeat(activity.id, user2) + + conn_res = + build_conn() + |> assign(:user, user3) + |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"])) + |> get("/api/v1/statuses/#{reblog_activity1.id}") + + assert %{ + "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2}, + "reblogged" => false, + "favourited" => false, + "bookmarked" => false + } = json_response(conn_res, 200) + + conn_res = + build_conn() + |> assign(:user, user2) + |> assign(:token, insert(:oauth_token, user: user2, scopes: ["read:statuses"])) + |> get("/api/v1/statuses/#{reblog_activity1.id}") + + assert %{ + "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2}, + "reblogged" => true, + "favourited" => true, + "bookmarked" => true + } = json_response(conn_res, 200) + + assert to_string(activity.id) == id + end + end + + describe "unreblogging" do + setup do: oauth_access(["write:statuses"]) + + test "unreblogs and returns the unreblogged status", %{user: user, conn: conn} do + activity = insert(:note_activity) + + {:ok, _, _} = CommonAPI.repeat(activity.id, user) + + conn = post(conn, "/api/v1/statuses/#{activity.id}/unreblog") + + assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200) + + assert to_string(activity.id) == id + end + + test "returns 404 error when activity does not exist", %{conn: conn} do + conn = post(conn, "/api/v1/statuses/foo/unreblog") + + assert json_response(conn, 404) == %{"error" => "Record not found"} + end + end + + describe "favoriting" do + setup do: oauth_access(["write:favourites"]) + + test "favs a status and returns it", %{conn: conn} do + activity = insert(:note_activity) + + conn = post(conn, "/api/v1/statuses/#{activity.id}/favourite") + + assert %{"id" => id, "favourites_count" => 1, "favourited" => true} = + json_response(conn, 200) + + assert to_string(activity.id) == id + end + + test "favoriting twice will just return 200", %{conn: conn} do + activity = insert(:note_activity) + + post(conn, "/api/v1/statuses/#{activity.id}/favourite") + assert post(conn, "/api/v1/statuses/#{activity.id}/favourite") |> json_response(200) + end + + test "returns 404 error for a wrong id", %{conn: conn} do + conn = post(conn, "/api/v1/statuses/1/favourite") + + assert json_response(conn, 404) == %{"error" => "Record not found"} + end + end + + describe "unfavoriting" do + setup do: oauth_access(["write:favourites"]) + + test "unfavorites a status and returns it", %{user: user, conn: conn} do + activity = insert(:note_activity) + + {:ok, _, _} = CommonAPI.favorite(activity.id, user) + + conn = post(conn, "/api/v1/statuses/#{activity.id}/unfavourite") + + assert %{"id" => id, "favourites_count" => 0, "favourited" => false} = + json_response(conn, 200) + + assert to_string(activity.id) == id + end + + test "returns 404 error for a wrong id", %{conn: conn} do + conn = post(conn, "/api/v1/statuses/1/unfavourite") + + assert json_response(conn, 404) == %{"error" => "Record not found"} + end + end + + describe "pinned statuses" do + setup do: oauth_access(["write:accounts"]) + + setup %{user: user} do + {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"}) + + %{activity: activity} + end + + clear_config([:instance, :max_pinned_statuses]) do + Config.put([:instance, :max_pinned_statuses], 1) + end + + test "pin status", %{conn: conn, user: user, activity: activity} do + id_str = to_string(activity.id) + + assert %{"id" => ^id_str, "pinned" => true} = + conn + |> post("/api/v1/statuses/#{activity.id}/pin") + |> json_response(200) + + assert [%{"id" => ^id_str, "pinned" => true}] = + conn + |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") + |> json_response(200) + end + + test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do + {:ok, dm} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"}) + + conn = post(conn, "/api/v1/statuses/#{dm.id}/pin") + + assert json_response(conn, 400) == %{"error" => "Could not pin"} + end + + test "unpin status", %{conn: conn, user: user, activity: activity} do + {:ok, _} = CommonAPI.pin(activity.id, user) + user = refresh_record(user) + + id_str = to_string(activity.id) + + assert %{"id" => ^id_str, "pinned" => false} = + conn + |> assign(:user, user) + |> post("/api/v1/statuses/#{activity.id}/unpin") + |> json_response(200) + + assert [] = + conn + |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") + |> json_response(200) + end + + test "/unpin: returns 400 error when activity is not exist", %{conn: conn} do + conn = post(conn, "/api/v1/statuses/1/unpin") + + assert json_response(conn, 400) == %{"error" => "Could not unpin"} + end + + test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do + {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"}) + + id_str_one = to_string(activity_one.id) + + assert %{"id" => ^id_str_one, "pinned" => true} = + conn + |> post("/api/v1/statuses/#{id_str_one}/pin") + |> json_response(200) + + user = refresh_record(user) + + assert %{"error" => "You have already pinned the maximum number of statuses"} = + conn + |> assign(:user, user) + |> post("/api/v1/statuses/#{activity_two.id}/pin") + |> json_response(400) + end + end + + describe "cards" do + setup do + Config.put([:rich_media, :enabled], true) + + oauth_access(["read:statuses"]) + end + + test "returns rich-media card", %{conn: conn, user: user} do + Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"}) + + card_data = %{ + "image" => "http://ia.media-imdb.com/images/rock.jpg", + "provider_name" => "example.com", + "provider_url" => "https://example.com", + "title" => "The Rock", + "type" => "link", + "url" => "https://example.com/ogp", + "description" => + "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.", + "pleroma" => %{ + "opengraph" => %{ + "image" => "http://ia.media-imdb.com/images/rock.jpg", + "title" => "The Rock", + "type" => "video.movie", + "url" => "https://example.com/ogp", + "description" => + "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer." + } + } + } + + response = + conn + |> get("/api/v1/statuses/#{activity.id}/card") + |> json_response(200) + + assert response == card_data + + # works with private posts + {:ok, activity} = + CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"}) + + response_two = + conn + |> get("/api/v1/statuses/#{activity.id}/card") + |> json_response(200) + + assert response_two == card_data + end + + test "replaces missing description with an empty string", %{conn: conn, user: user} do + Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + + {:ok, activity} = + CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"}) + + response = + conn + |> get("/api/v1/statuses/#{activity.id}/card") + |> json_response(:ok) + + assert response == %{ + "type" => "link", + "title" => "Pleroma", + "description" => "", + "image" => nil, + "provider_name" => "example.com", + "provider_url" => "https://example.com", + "url" => "https://example.com/ogp-missing-data", + "pleroma" => %{ + "opengraph" => %{ + "title" => "Pleroma", + "type" => "website", + "url" => "https://example.com/ogp-missing-data" + } + } + } + end + end + + test "bookmarks" do + %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"]) + author = insert(:user) + + {:ok, activity1} = + CommonAPI.post(author, %{ + "status" => "heweoo?" + }) + + {:ok, activity2} = + CommonAPI.post(author, %{ + "status" => "heweoo!" + }) + + response1 = post(conn, "/api/v1/statuses/#{activity1.id}/bookmark") + + assert json_response(response1, 200)["bookmarked"] == true + + response2 = post(conn, "/api/v1/statuses/#{activity2.id}/bookmark") + + assert json_response(response2, 200)["bookmarked"] == true + + bookmarks = get(conn, "/api/v1/bookmarks") + + assert [json_response(response2, 200), json_response(response1, 200)] == + json_response(bookmarks, 200) + + response1 = post(conn, "/api/v1/statuses/#{activity1.id}/unbookmark") + + assert json_response(response1, 200)["bookmarked"] == false + + bookmarks = get(conn, "/api/v1/bookmarks") + + assert [json_response(response2, 200)] == json_response(bookmarks, 200) + end + + describe "conversation muting" do + setup do: oauth_access(["write:mutes"]) + + setup do + post_user = insert(:user) + {:ok, activity} = CommonAPI.post(post_user, %{"status" => "HIE"}) + %{activity: activity} + end + + test "mute conversation", %{conn: conn, activity: activity} do + id_str = to_string(activity.id) + + assert %{"id" => ^id_str, "muted" => true} = + conn + |> post("/api/v1/statuses/#{activity.id}/mute") + |> json_response(200) + end + + test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do + {:ok, _} = CommonAPI.add_mute(user, activity) + + conn = post(conn, "/api/v1/statuses/#{activity.id}/mute") + + assert json_response(conn, 400) == %{"error" => "conversation is already muted"} + end + + test "unmute conversation", %{conn: conn, user: user, activity: activity} do + {:ok, _} = CommonAPI.add_mute(user, activity) + + id_str = to_string(activity.id) + + assert %{"id" => ^id_str, "muted" => false} = + conn + # |> assign(:user, user) + |> post("/api/v1/statuses/#{activity.id}/unmute") + |> json_response(200) + end + end + + test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do + user1 = insert(:user) + user2 = insert(:user) + user3 = insert(:user) + + {:ok, replied_to} = CommonAPI.post(user1, %{"status" => "cofe"}) + + # Reply to status from another user + conn1 = + conn + |> assign(:user, user2) + |> assign(:token, insert(:oauth_token, user: user2, scopes: ["write:statuses"])) + |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id}) + + assert %{"content" => "xD", "id" => id} = json_response(conn1, 200) + + activity = Activity.get_by_id_with_object(id) + + assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"] + assert Activity.get_in_reply_to_activity(activity).id == replied_to.id + + # Reblog from the third user + conn2 = + conn + |> assign(:user, user3) + |> assign(:token, insert(:oauth_token, user: user3, scopes: ["write:statuses"])) + |> post("/api/v1/statuses/#{activity.id}/reblog") + + assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} = + json_response(conn2, 200) + + assert to_string(activity.id) == id + + # Getting third user status + conn3 = + conn + |> assign(:user, user3) + |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"])) + |> get("api/v1/timelines/home") + + [reblogged_activity] = json_response(conn3, 200) + + assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id + + replied_to_user = User.get_by_ap_id(replied_to.data["actor"]) + assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id + end + + describe "GET /api/v1/statuses/:id/favourited_by" do + setup do: oauth_access(["read:accounts"]) + + setup %{user: user} do + {:ok, activity} = CommonAPI.post(user, %{"status" => "test"}) + + %{activity: activity} + end + + test "returns users who have favorited the status", %{conn: conn, activity: activity} do + other_user = insert(:user) + {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + + response = + conn + |> get("/api/v1/statuses/#{activity.id}/favourited_by") + |> json_response(:ok) + + [%{"id" => id}] = response + + assert id == other_user.id + end + + test "returns empty array when status has not been favorited yet", %{ + conn: conn, + activity: activity + } do + response = + conn + |> get("/api/v1/statuses/#{activity.id}/favourited_by") + |> json_response(:ok) + + assert Enum.empty?(response) + end + + test "does not return users who have favorited the status but are blocked", %{ + conn: %{assigns: %{user: user}} = conn, + activity: activity + } do + other_user = insert(:user) + {:ok, _user_relationship} = User.block(user, other_user) + + {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + + response = + conn + |> get("/api/v1/statuses/#{activity.id}/favourited_by") + |> json_response(:ok) + + assert Enum.empty?(response) + end + + test "does not fail on an unauthenticated request", %{activity: activity} do + other_user = insert(:user) + {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + + response = + build_conn() + |> get("/api/v1/statuses/#{activity.id}/favourited_by") + |> json_response(:ok) + + [%{"id" => id}] = response + assert id == other_user.id + end + + test "requires authentication for private posts", %{user: user} do + other_user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => "@#{other_user.nickname} wanna get some #cofe together?", + "visibility" => "direct" + }) + + {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + + favourited_by_url = "/api/v1/statuses/#{activity.id}/favourited_by" + + build_conn() + |> get(favourited_by_url) + |> json_response(404) + + conn = + build_conn() + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"])) + + conn + |> assign(:token, nil) + |> get(favourited_by_url) + |> json_response(404) + + response = + conn + |> get(favourited_by_url) + |> json_response(200) + + [%{"id" => id}] = response + assert id == other_user.id + end + end + + describe "GET /api/v1/statuses/:id/reblogged_by" do + setup do: oauth_access(["read:accounts"]) + + setup %{user: user} do + {:ok, activity} = CommonAPI.post(user, %{"status" => "test"}) + + %{activity: activity} + end + + test "returns users who have reblogged the status", %{conn: conn, activity: activity} do + other_user = insert(:user) + {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) + + response = + conn + |> get("/api/v1/statuses/#{activity.id}/reblogged_by") + |> json_response(:ok) + + [%{"id" => id}] = response + + assert id == other_user.id + end + + test "returns empty array when status has not been reblogged yet", %{ + conn: conn, + activity: activity + } do + response = + conn + |> get("/api/v1/statuses/#{activity.id}/reblogged_by") + |> json_response(:ok) + + assert Enum.empty?(response) + end + + test "does not return users who have reblogged the status but are blocked", %{ + conn: %{assigns: %{user: user}} = conn, + activity: activity + } do + other_user = insert(:user) + {:ok, _user_relationship} = User.block(user, other_user) + + {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) + + response = + conn + |> get("/api/v1/statuses/#{activity.id}/reblogged_by") + |> json_response(:ok) + + assert Enum.empty?(response) + end + + test "does not return users who have reblogged the status privately", %{ + conn: conn, + activity: activity + } do + other_user = insert(:user) + + {:ok, _, _} = CommonAPI.repeat(activity.id, other_user, %{"visibility" => "private"}) + + response = + conn + |> get("/api/v1/statuses/#{activity.id}/reblogged_by") + |> json_response(:ok) + + assert Enum.empty?(response) + end + + test "does not fail on an unauthenticated request", %{activity: activity} do + other_user = insert(:user) + {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) + + response = + build_conn() + |> get("/api/v1/statuses/#{activity.id}/reblogged_by") + |> json_response(:ok) + + [%{"id" => id}] = response + assert id == other_user.id + end + + test "requires authentication for private posts", %{user: user} do + other_user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => "@#{other_user.nickname} wanna get some #cofe together?", + "visibility" => "direct" + }) + + build_conn() + |> get("/api/v1/statuses/#{activity.id}/reblogged_by") + |> json_response(404) + + response = + build_conn() + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"])) + |> get("/api/v1/statuses/#{activity.id}/reblogged_by") + |> json_response(200) + + assert [] == response + end + end + + test "context" do + user = insert(:user) + + {:ok, %{id: id1}} = CommonAPI.post(user, %{"status" => "1"}) + {:ok, %{id: id2}} = CommonAPI.post(user, %{"status" => "2", "in_reply_to_status_id" => id1}) + {:ok, %{id: id3}} = CommonAPI.post(user, %{"status" => "3", "in_reply_to_status_id" => id2}) + {:ok, %{id: id4}} = CommonAPI.post(user, %{"status" => "4", "in_reply_to_status_id" => id3}) + {:ok, %{id: id5}} = CommonAPI.post(user, %{"status" => "5", "in_reply_to_status_id" => id4}) + + response = + build_conn() + |> get("/api/v1/statuses/#{id3}/context") + |> json_response(:ok) + + assert %{ + "ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}], + "descendants" => [%{"id" => ^id4}, %{"id" => ^id5}] + } = response + end + + test "returns the favorites of a user" do + %{user: user, conn: conn} = oauth_access(["read:favourites"]) + other_user = insert(:user) + + {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"}) + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"}) + + {:ok, _, _} = CommonAPI.favorite(activity.id, user) + + first_conn = get(conn, "/api/v1/favourites") + + assert [status] = json_response(first_conn, 200) + assert status["id"] == to_string(activity.id) + + assert [{"link", _link_header}] = + Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end) + + # Honours query params + {:ok, second_activity} = + CommonAPI.post(other_user, %{ + "status" => + "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful." + }) + + {:ok, _, _} = CommonAPI.favorite(second_activity.id, user) + + last_like = status["id"] + + second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like}") + + assert [second_status] = json_response(second_conn, 200) + assert second_status["id"] == to_string(second_activity.id) + + third_conn = get(conn, "/api/v1/favourites?limit=0") + + assert [] = json_response(third_conn, 200) + end + + test "expires_at is nil for another user" do + %{conn: conn, user: user} = oauth_access(["read:statuses"]) + {:ok, activity} = CommonAPI.post(user, %{"status" => "foobar", "expires_in" => 1_000_000}) + + expires_at = + activity.id + |> ActivityExpiration.get_by_activity_id() + |> Map.get(:scheduled_at) + |> NaiveDateTime.to_iso8601() + + assert %{"pleroma" => %{"expires_at" => ^expires_at}} = + conn |> get("/api/v1/statuses/#{activity.id}") |> json_response(:ok) + + %{conn: conn} = oauth_access(["read:statuses"]) + + assert %{"pleroma" => %{"expires_at" => nil}} = + conn |> get("/api/v1/statuses/#{activity.id}") |> json_response(:ok) + end +end diff --git a/test/web/mastodon_api/controllers/subscription_controller_test.exs b/test/web/mastodon_api/controllers/subscription_controller_test.exs index 7dfb02f63..987158a74 100644 --- a/test/web/mastodon_api/controllers/subscription_controller_test.exs +++ b/test/web/mastodon_api/controllers/subscription_controller_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do diff --git a/test/web/mastodon_api/controllers/suggestion_controller_test.exs b/test/web/mastodon_api/controllers/suggestion_controller_test.exs new file mode 100644 index 000000000..c697a39f8 --- /dev/null +++ b/test/web/mastodon_api/controllers/suggestion_controller_test.exs @@ -0,0 +1,46 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.SuggestionControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Config + + import Pleroma.Factory + import Tesla.Mock + + setup do: oauth_access(["read"]) + + setup %{user: user} do + other_user = insert(:user) + host = Config.get([Pleroma.Web.Endpoint, :url, :host]) + url500 = "http://test500?#{host}&#{user.nickname}" + url200 = "http://test200?#{host}&#{user.nickname}" + + mock(fn + %{method: :get, url: ^url500} -> + %Tesla.Env{status: 500, body: "bad request"} + + %{method: :get, url: ^url200} -> + %Tesla.Env{ + status: 200, + body: + ~s([{"acct":"yj455","avatar":"https://social.heldscal.la/avatar/201.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/201.jpeg"}, {"acct":"#{ + other_user.ap_id + }","avatar":"https://social.heldscal.la/avatar/202.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/202.jpeg"}]) + } + end) + + [other_user: other_user] + end + + test "returns empty result", %{conn: conn} do + res = + conn + |> get("/api/v1/suggestions") + |> json_response(200) + + assert res == [] + end +end diff --git a/test/web/mastodon_api/controllers/timeline_controller_test.exs b/test/web/mastodon_api/controllers/timeline_controller_test.exs new file mode 100644 index 000000000..2c03b0a75 --- /dev/null +++ b/test/web/mastodon_api/controllers/timeline_controller_test.exs @@ -0,0 +1,289 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do + use Pleroma.Web.ConnCase + + import Pleroma.Factory + import Tesla.Mock + + alias Pleroma.Config + alias Pleroma.User + alias Pleroma.Web.CommonAPI + + clear_config([:instance, :public]) + + setup do + mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + + describe "home" do + setup do: oauth_access(["read:statuses"]) + + test "the home timeline", %{user: user, conn: conn} do + following = insert(:user) + + {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"}) + + ret_conn = get(conn, "/api/v1/timelines/home") + + assert Enum.empty?(json_response(ret_conn, :ok)) + + {:ok, _user} = User.follow(user, following) + + conn = get(conn, "/api/v1/timelines/home") + + assert [%{"content" => "test"}] = json_response(conn, :ok) + end + + test "the home timeline when the direct messages are excluded", %{user: user, conn: conn} do + {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"}) + {:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) + + {:ok, unlisted_activity} = + CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"}) + + {:ok, private_activity} = + CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + + conn = get(conn, "/api/v1/timelines/home", %{"exclude_visibilities" => ["direct"]}) + + assert status_ids = json_response(conn, :ok) |> Enum.map(& &1["id"]) + assert public_activity.id in status_ids + assert unlisted_activity.id in status_ids + assert private_activity.id in status_ids + refute direct_activity.id in status_ids + end + end + + describe "public" do + @tag capture_log: true + test "the public timeline", %{conn: conn} do + following = insert(:user) + + {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"}) + + _activity = insert(:note_activity, local: false) + + conn = get(conn, "/api/v1/timelines/public", %{"local" => "False"}) + + assert length(json_response(conn, :ok)) == 2 + + conn = get(build_conn(), "/api/v1/timelines/public", %{"local" => "True"}) + + assert [%{"content" => "test"}] = json_response(conn, :ok) + + conn = get(build_conn(), "/api/v1/timelines/public", %{"local" => "1"}) + + assert [%{"content" => "test"}] = json_response(conn, :ok) + end + + test "the public timeline when public is set to false", %{conn: conn} do + Config.put([:instance, :public], false) + + assert %{"error" => "This resource requires authentication."} == + conn + |> get("/api/v1/timelines/public", %{"local" => "False"}) + |> json_response(:forbidden) + end + + test "the public timeline includes only public statuses for an authenticated user" do + %{user: user, conn: conn} = oauth_access(["read:statuses"]) + + {:ok, _activity} = CommonAPI.post(user, %{"status" => "test"}) + {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "private"}) + {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "unlisted"}) + {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"}) + + res_conn = get(conn, "/api/v1/timelines/public") + assert length(json_response(res_conn, 200)) == 1 + end + end + + describe "direct" do + test "direct timeline", %{conn: conn} do + user_one = insert(:user) + user_two = insert(:user) + + {:ok, user_two} = User.follow(user_two, user_one) + + {:ok, direct} = + CommonAPI.post(user_one, %{ + "status" => "Hi @#{user_two.nickname}!", + "visibility" => "direct" + }) + + {:ok, _follower_only} = + CommonAPI.post(user_one, %{ + "status" => "Hi @#{user_two.nickname}!", + "visibility" => "private" + }) + + conn_user_two = + conn + |> assign(:user, user_two) + |> assign(:token, insert(:oauth_token, user: user_two, scopes: ["read:statuses"])) + + # Only direct should be visible here + res_conn = get(conn_user_two, "api/v1/timelines/direct") + + [status] = json_response(res_conn, :ok) + + assert %{"visibility" => "direct"} = status + assert status["url"] != direct.data["id"] + + # User should be able to see their own direct message + res_conn = + build_conn() + |> assign(:user, user_one) + |> assign(:token, insert(:oauth_token, user: user_one, scopes: ["read:statuses"])) + |> get("api/v1/timelines/direct") + + [status] = json_response(res_conn, :ok) + + assert %{"visibility" => "direct"} = status + + # Both should be visible here + res_conn = get(conn_user_two, "api/v1/timelines/home") + + [_s1, _s2] = json_response(res_conn, :ok) + + # Test pagination + Enum.each(1..20, fn _ -> + {:ok, _} = + CommonAPI.post(user_one, %{ + "status" => "Hi @#{user_two.nickname}!", + "visibility" => "direct" + }) + end) + + res_conn = get(conn_user_two, "api/v1/timelines/direct") + + statuses = json_response(res_conn, :ok) + assert length(statuses) == 20 + + res_conn = + get(conn_user_two, "api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]}) + + [status] = json_response(res_conn, :ok) + + assert status["url"] != direct.data["id"] + end + + test "doesn't include DMs from blocked users" do + %{user: blocker, conn: conn} = oauth_access(["read:statuses"]) + blocked = insert(:user) + other_user = insert(:user) + {:ok, _user_relationship} = User.block(blocker, blocked) + + {:ok, _blocked_direct} = + CommonAPI.post(blocked, %{ + "status" => "Hi @#{blocker.nickname}!", + "visibility" => "direct" + }) + + {:ok, direct} = + CommonAPI.post(other_user, %{ + "status" => "Hi @#{blocker.nickname}!", + "visibility" => "direct" + }) + + res_conn = get(conn, "api/v1/timelines/direct") + + [status] = json_response(res_conn, :ok) + assert status["id"] == direct.id + end + end + + describe "list" do + setup do: oauth_access(["read:lists"]) + + test "list timeline", %{user: user, conn: conn} do + other_user = insert(:user) + {:ok, _activity_one} = CommonAPI.post(user, %{"status" => "Marisa is cute."}) + {:ok, activity_two} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."}) + {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.follow(list, other_user) + + conn = get(conn, "/api/v1/timelines/list/#{list.id}") + + assert [%{"id" => id}] = json_response(conn, :ok) + + assert id == to_string(activity_two.id) + end + + test "list timeline does not leak non-public statuses for unfollowed users", %{ + user: user, + conn: conn + } do + other_user = insert(:user) + {:ok, activity_one} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."}) + + {:ok, _activity_two} = + CommonAPI.post(other_user, %{ + "status" => "Marisa is cute.", + "visibility" => "private" + }) + + {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.follow(list, other_user) + + conn = get(conn, "/api/v1/timelines/list/#{list.id}") + + assert [%{"id" => id}] = json_response(conn, :ok) + + assert id == to_string(activity_one.id) + end + end + + describe "hashtag" do + setup do: oauth_access(["n/a"]) + + @tag capture_log: true + test "hashtag timeline", %{conn: conn} do + following = insert(:user) + + {:ok, activity} = CommonAPI.post(following, %{"status" => "test #2hu"}) + + nconn = get(conn, "/api/v1/timelines/tag/2hu") + + assert [%{"id" => id}] = json_response(nconn, :ok) + + assert id == to_string(activity.id) + + # works for different capitalization too + nconn = get(conn, "/api/v1/timelines/tag/2HU") + + assert [%{"id" => id}] = json_response(nconn, :ok) + + assert id == to_string(activity.id) + end + + test "multi-hashtag timeline", %{conn: conn} do + user = insert(:user) + + {:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"}) + {:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"}) + {:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"}) + + any_test = get(conn, "/api/v1/timelines/tag/test", %{"any" => ["test1"]}) + + [status_none, status_test1, status_test] = json_response(any_test, :ok) + + assert to_string(activity_test.id) == status_test["id"] + assert to_string(activity_test1.id) == status_test1["id"] + assert to_string(activity_none.id) == status_none["id"] + + restricted_test = + get(conn, "/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]}) + + assert [status_test1] == json_response(restricted_test, :ok) + + all_test = get(conn, "/api/v1/timelines/tag/test", %{"all" => ["none"]}) + + assert [status_none] == json_response(all_test, :ok) + end + end +end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index dad832a66..75f184242 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -1,4066 +1,41 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do use Pleroma.Web.ConnCase - alias Ecto.Changeset - alias Pleroma.Activity - alias Pleroma.ActivityExpiration - alias Pleroma.Config - alias Pleroma.Notification - alias Pleroma.Object - alias Pleroma.Repo - alias Pleroma.ScheduledActivity - alias Pleroma.User - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.CommonAPI - alias Pleroma.Web.MastodonAPI.FilterView - alias Pleroma.Web.OAuth.App - alias Pleroma.Web.OAuth.Token - alias Pleroma.Web.OStatus - alias Pleroma.Web.Push - import Pleroma.Factory - import ExUnit.CaptureLog - import Tesla.Mock - import Swoosh.TestAssertions - - @image "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7" - - setup do - mock(fn env -> apply(HttpRequestMock, :request, [env]) end) - :ok - end - - clear_config([:instance, :public]) - clear_config([:rich_media, :enabled]) - - test "the home timeline", %{conn: conn} do - user = insert(:user) - following = insert(:user) - - {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"}) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/timelines/home") - - assert Enum.empty?(json_response(conn, 200)) - - {:ok, user} = User.follow(user, following) - - conn = - build_conn() - |> assign(:user, user) - |> get("/api/v1/timelines/home") - - assert [%{"content" => "test"}] = json_response(conn, 200) - end - - test "the public timeline", %{conn: conn} do - following = insert(:user) - - capture_log(fn -> - {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"}) - - {:ok, [_activity]} = - OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873") - - conn = - conn - |> get("/api/v1/timelines/public", %{"local" => "False"}) - - assert length(json_response(conn, 200)) == 2 - - conn = - build_conn() - |> get("/api/v1/timelines/public", %{"local" => "True"}) - - assert [%{"content" => "test"}] = json_response(conn, 200) - - conn = - build_conn() - |> get("/api/v1/timelines/public", %{"local" => "1"}) - - assert [%{"content" => "test"}] = json_response(conn, 200) - end) - end - - test "the public timeline when public is set to false", %{conn: conn} do - Config.put([:instance, :public], false) - - assert conn - |> get("/api/v1/timelines/public", %{"local" => "False"}) - |> json_response(403) == %{"error" => "This resource requires authentication."} - end - - test "the public timeline includes only public statuses for an authenticated user" do - user = insert(:user) - - conn = - build_conn() - |> assign(:user, user) - - {:ok, _activity} = CommonAPI.post(user, %{"status" => "test"}) - {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "private"}) - {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "unlisted"}) - {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"}) - - res_conn = get(conn, "/api/v1/timelines/public") - assert length(json_response(res_conn, 200)) == 1 - end - - describe "posting statuses" do - setup do - user = insert(:user) - - conn = - build_conn() - |> assign(:user, user) - - [conn: conn] - end - - test "posting a status", %{conn: conn} do - idempotency_key = "Pikachu rocks!" - - conn_one = - conn - |> put_req_header("idempotency-key", idempotency_key) - |> post("/api/v1/statuses", %{ - "status" => "cofe", - "spoiler_text" => "2hu", - "sensitive" => "false" - }) - - {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key) - # Six hours - assert ttl > :timer.seconds(6 * 60 * 60 - 1) - - assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} = - json_response(conn_one, 200) - - assert Activity.get_by_id(id) - - conn_two = - conn - |> put_req_header("idempotency-key", idempotency_key) - |> post("/api/v1/statuses", %{ - "status" => "cofe", - "spoiler_text" => "2hu", - "sensitive" => "false" - }) - - assert %{"id" => second_id} = json_response(conn_two, 200) - assert id == second_id - - conn_three = - conn - |> post("/api/v1/statuses", %{ - "status" => "cofe", - "spoiler_text" => "2hu", - "sensitive" => "false" - }) - - assert %{"id" => third_id} = json_response(conn_three, 200) - refute id == third_id - - # An activity that will expire: - # 2 hours - expires_in = 120 * 60 - - conn_four = - conn - |> post("api/v1/statuses", %{ - "status" => "oolong", - "expires_in" => expires_in - }) - - assert fourth_response = %{"id" => fourth_id} = json_response(conn_four, 200) - assert activity = Activity.get_by_id(fourth_id) - assert expiration = ActivityExpiration.get_by_activity_id(fourth_id) - - estimated_expires_at = - NaiveDateTime.utc_now() - |> NaiveDateTime.add(expires_in) - |> NaiveDateTime.truncate(:second) - - # This assert will fail if the test takes longer than a minute. I sure hope it never does: - assert abs(NaiveDateTime.diff(expiration.scheduled_at, estimated_expires_at, :second)) < 60 - - assert fourth_response["pleroma"]["expires_at"] == - NaiveDateTime.to_iso8601(expiration.scheduled_at) - end - - test "replying to a status", %{conn: conn} do - user = insert(:user) - {:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"}) - - conn = - conn - |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id}) - - assert %{"content" => "xD", "id" => id} = json_response(conn, 200) - - activity = Activity.get_by_id(id) - - assert activity.data["context"] == replied_to.data["context"] - assert Activity.get_in_reply_to_activity(activity).id == replied_to.id - end - - test "replying to a direct message with visibility other than direct", %{conn: conn} do - user = insert(:user) - {:ok, replied_to} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"}) - - Enum.each(["public", "private", "unlisted"], fn visibility -> - conn = - conn - |> post("/api/v1/statuses", %{ - "status" => "@#{user.nickname} hey", - "in_reply_to_id" => replied_to.id, - "visibility" => visibility - }) - - assert json_response(conn, 422) == %{"error" => "The message visibility must be direct"} - end) - end - - test "posting a status with an invalid in_reply_to_id", %{conn: conn} do - conn = - conn - |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""}) - - assert %{"content" => "xD", "id" => id} = json_response(conn, 200) - assert Activity.get_by_id(id) - end - - test "posting a sensitive status", %{conn: conn} do - conn = - conn - |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true}) - - assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200) - assert Activity.get_by_id(id) - end - - test "posting a fake status", %{conn: conn} do - real_conn = - conn - |> post("/api/v1/statuses", %{ - "status" => - "\"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" - }) - - real_status = json_response(real_conn, 200) - - assert real_status - assert Object.get_by_ap_id(real_status["uri"]) - - real_status = - real_status - |> Map.put("id", nil) - |> Map.put("url", nil) - |> Map.put("uri", nil) - |> Map.put("created_at", nil) - |> Kernel.put_in(["pleroma", "conversation_id"], nil) - - fake_conn = - conn - |> post("/api/v1/statuses", %{ - "status" => - "\"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", - "preview" => true - }) - - fake_status = json_response(fake_conn, 200) - - assert fake_status - refute Object.get_by_ap_id(fake_status["uri"]) - - fake_status = - fake_status - |> Map.put("id", nil) - |> Map.put("url", nil) - |> Map.put("uri", nil) - |> Map.put("created_at", nil) - |> Kernel.put_in(["pleroma", "conversation_id"], nil) - - assert real_status == fake_status - end - - test "posting a status with OGP link preview", %{conn: conn} do - Config.put([:rich_media, :enabled], true) - - conn = - conn - |> post("/api/v1/statuses", %{ - "status" => "https://example.com/ogp" - }) - - assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200) - assert Activity.get_by_id(id) - end - - test "posting a direct status", %{conn: conn} do - user2 = insert(:user) - content = "direct cofe @#{user2.nickname}" - - conn = - conn - |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"}) - - assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200) - assert activity = Activity.get_by_id(id) - assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id] - assert activity.data["to"] == [user2.ap_id] - assert activity.data["cc"] == [] - end - end - - describe "posting polls" do - test "posting a poll", %{conn: conn} do - user = insert(:user) - time = NaiveDateTime.utc_now() - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{ - "status" => "Who is the #bestgrill?", - "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420} - }) - - response = json_response(conn, 200) - - assert Enum.all?(response["poll"]["options"], fn %{"title" => title} -> - title in ["Rei", "Asuka", "Misato"] - end) - - assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430 - refute response["poll"]["expred"] - end - - test "option limit is enforced", %{conn: conn} do - user = insert(:user) - limit = Config.get([:instance, :poll_limits, :max_options]) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{ - "status" => "desu~", - "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1} - }) - - %{"error" => error} = json_response(conn, 422) - assert error == "Poll can't contain more than #{limit} options" - end - - test "option character limit is enforced", %{conn: conn} do - user = insert(:user) - limit = Config.get([:instance, :poll_limits, :max_option_chars]) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{ - "status" => "...", - "poll" => %{ - "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)], - "expires_in" => 1 - } - }) - - %{"error" => error} = json_response(conn, 422) - assert error == "Poll options cannot be longer than #{limit} characters each" - end - - test "minimal date limit is enforced", %{conn: conn} do - user = insert(:user) - limit = Config.get([:instance, :poll_limits, :min_expiration]) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{ - "status" => "imagine arbitrary limits", - "poll" => %{ - "options" => ["this post was made by pleroma gang"], - "expires_in" => limit - 1 - } - }) - - %{"error" => error} = json_response(conn, 422) - assert error == "Expiration date is too soon" - end - - test "maximum date limit is enforced", %{conn: conn} do - user = insert(:user) - limit = Config.get([:instance, :poll_limits, :max_expiration]) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{ - "status" => "imagine arbitrary limits", - "poll" => %{ - "options" => ["this post was made by pleroma gang"], - "expires_in" => limit + 1 - } - }) - - %{"error" => error} = json_response(conn, 422) - assert error == "Expiration date is too far in the future" - end - end - - test "direct timeline", %{conn: conn} do - user_one = insert(:user) - user_two = insert(:user) - - {:ok, user_two} = User.follow(user_two, user_one) - - {:ok, direct} = - CommonAPI.post(user_one, %{ - "status" => "Hi @#{user_two.nickname}!", - "visibility" => "direct" - }) - - {:ok, _follower_only} = - CommonAPI.post(user_one, %{ - "status" => "Hi @#{user_two.nickname}!", - "visibility" => "private" - }) - - # Only direct should be visible here - res_conn = - conn - |> assign(:user, user_two) - |> get("api/v1/timelines/direct") - - [status] = json_response(res_conn, 200) - - assert %{"visibility" => "direct"} = status - assert status["url"] != direct.data["id"] - - # User should be able to see their own direct message - res_conn = - build_conn() - |> assign(:user, user_one) - |> get("api/v1/timelines/direct") - - [status] = json_response(res_conn, 200) - - assert %{"visibility" => "direct"} = status - - # Both should be visible here - res_conn = - conn - |> assign(:user, user_two) - |> get("api/v1/timelines/home") - - [_s1, _s2] = json_response(res_conn, 200) - - # Test pagination - Enum.each(1..20, fn _ -> - {:ok, _} = - CommonAPI.post(user_one, %{ - "status" => "Hi @#{user_two.nickname}!", - "visibility" => "direct" - }) - end) - - res_conn = - conn - |> assign(:user, user_two) - |> get("api/v1/timelines/direct") - - statuses = json_response(res_conn, 200) - assert length(statuses) == 20 - - res_conn = - conn - |> assign(:user, user_two) - |> get("api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]}) - - [status] = json_response(res_conn, 200) - - assert status["url"] != direct.data["id"] - end - - test "Conversations", %{conn: conn} do - user_one = insert(:user) - user_two = insert(:user) - user_three = insert(:user) - - {:ok, user_two} = User.follow(user_two, user_one) - - {:ok, direct} = - CommonAPI.post(user_one, %{ - "status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!", - "visibility" => "direct" - }) - - {:ok, _follower_only} = - CommonAPI.post(user_one, %{ - "status" => "Hi @#{user_two.nickname}!", - "visibility" => "private" - }) - - res_conn = - conn - |> assign(:user, user_one) - |> get("/api/v1/conversations") - - assert response = json_response(res_conn, 200) - - assert [ - %{ - "id" => res_id, - "accounts" => res_accounts, - "last_status" => res_last_status, - "unread" => unread - } - ] = response - - account_ids = Enum.map(res_accounts, & &1["id"]) - assert length(res_accounts) == 2 - assert user_two.id in account_ids - assert user_three.id in account_ids - assert is_binary(res_id) - assert unread == true - assert res_last_status["id"] == direct.id - - # Apparently undocumented API endpoint - res_conn = - conn - |> assign(:user, user_one) - |> post("/api/v1/conversations/#{res_id}/read") - - assert response = json_response(res_conn, 200) - assert length(response["accounts"]) == 2 - assert response["last_status"]["id"] == direct.id - assert response["unread"] == false - - # (vanilla) Mastodon frontend behaviour - res_conn = - conn - |> assign(:user, user_one) - |> get("/api/v1/statuses/#{res_last_status["id"]}/context") - - assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200) - end - - test "doesn't include DMs from blocked users", %{conn: conn} do - blocker = insert(:user) - blocked = insert(:user) - user = insert(:user) - {:ok, blocker} = User.block(blocker, blocked) - - {:ok, _blocked_direct} = - CommonAPI.post(blocked, %{ - "status" => "Hi @#{blocker.nickname}!", - "visibility" => "direct" - }) - - {:ok, direct} = - CommonAPI.post(user, %{ - "status" => "Hi @#{blocker.nickname}!", - "visibility" => "direct" - }) - - res_conn = - conn - |> assign(:user, user) - |> get("api/v1/timelines/direct") - - [status] = json_response(res_conn, 200) - assert status["id"] == direct.id - end - - test "verify_credentials", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/verify_credentials") - - response = json_response(conn, 200) - - assert %{"id" => id, "source" => %{"privacy" => "public"}} = response - assert response["pleroma"]["chat_token"] - assert id == to_string(user.id) - end - - test "verify_credentials default scope unlisted", %{conn: conn} do - user = insert(:user, %{info: %User.Info{default_scope: "unlisted"}}) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/verify_credentials") - - assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200) - assert id == to_string(user.id) - end - - test "apps/verify_credentials", %{conn: conn} do - token = insert(:oauth_token) - - conn = - conn - |> assign(:user, token.user) - |> assign(:token, token) - |> get("/api/v1/apps/verify_credentials") - - app = Repo.preload(token, :app).app - - expected = %{ - "name" => app.client_name, - "website" => app.website, - "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key) - } - - assert expected == json_response(conn, 200) - end - - test "user avatar can be set", %{conn: conn} do - user = insert(:user) - avatar_image = File.read!("test/fixtures/avatar_data_uri") - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: avatar_image}) - - user = refresh_record(user) - - assert %{ - "name" => _, - "type" => _, - "url" => [ - %{ - "href" => _, - "mediaType" => _, - "type" => _ - } - ] - } = user.avatar - - assert %{"url" => _} = json_response(conn, 200) - end - - test "user avatar can be reset", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: ""}) - - user = User.get_cached_by_id(user.id) - - assert user.avatar == nil - - assert %{"url" => nil} = json_response(conn, 200) - end - - test "can set profile banner", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => @image}) - - user = refresh_record(user) - assert user.info.banner["type"] == "Image" - - assert %{"url" => _} = json_response(conn, 200) - end - - test "can reset profile banner", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => ""}) - - user = refresh_record(user) - assert user.info.banner == %{} - - assert %{"url" => nil} = json_response(conn, 200) - end - - test "background image can be set", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => @image}) - - user = refresh_record(user) - assert user.info.background["type"] == "Image" - assert %{"url" => _} = json_response(conn, 200) - end - - test "background image can be reset", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => ""}) - - user = refresh_record(user) - assert user.info.background == %{} - assert %{"url" => nil} = json_response(conn, 200) - end - - test "creates an oauth app", %{conn: conn} do - user = insert(:user) - app_attrs = build(:oauth_app) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/apps", %{ - client_name: app_attrs.client_name, - redirect_uris: app_attrs.redirect_uris - }) - - [app] = Repo.all(App) - - expected = %{ - "name" => app.client_name, - "website" => app.website, - "client_id" => app.client_id, - "client_secret" => app.client_secret, - "id" => app.id |> to_string(), - "redirect_uri" => app.redirect_uris, - "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key) - } - - assert expected == json_response(conn, 200) - end - - test "get a status", %{conn: conn} do - activity = insert(:note_activity) - - conn = - conn - |> get("/api/v1/statuses/#{activity.id}") - - assert %{"id" => id} = json_response(conn, 200) - assert id == to_string(activity.id) - end - - test "get statuses by IDs", %{conn: conn} do - %{id: id1} = insert(:note_activity) - %{id: id2} = insert(:note_activity) - - query_string = "ids[]=#{id1}&ids[]=#{id2}" - conn = get(conn, "/api/v1/statuses/?#{query_string}") - - assert [%{"id" => ^id1}, %{"id" => ^id2}] = json_response(conn, :ok) - end - - describe "deleting a status" do - test "when you created it", %{conn: conn} do - activity = insert(:note_activity) - author = User.get_cached_by_ap_id(activity.data["actor"]) - - conn = - conn - |> assign(:user, author) - |> delete("/api/v1/statuses/#{activity.id}") - - assert %{} = json_response(conn, 200) - - refute Activity.get_by_id(activity.id) - end - - test "when you didn't create it", %{conn: conn} do - activity = insert(:note_activity) - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> delete("/api/v1/statuses/#{activity.id}") - - assert %{"error" => _} = json_response(conn, 403) - - assert Activity.get_by_id(activity.id) == activity - end - - test "when you're an admin or moderator", %{conn: conn} do - activity1 = insert(:note_activity) - activity2 = insert(:note_activity) - admin = insert(:user, info: %{is_admin: true}) - moderator = insert(:user, info: %{is_moderator: true}) - - res_conn = - conn - |> assign(:user, admin) - |> delete("/api/v1/statuses/#{activity1.id}") - - assert %{} = json_response(res_conn, 200) - - res_conn = - conn - |> assign(:user, moderator) - |> delete("/api/v1/statuses/#{activity2.id}") - - assert %{} = json_response(res_conn, 200) - - refute Activity.get_by_id(activity1.id) - refute Activity.get_by_id(activity2.id) - end - end - - describe "filters" do - test "creating a filter", %{conn: conn} do - user = insert(:user) - - filter = %Pleroma.Filter{ - phrase: "knights", - context: ["home"] - } - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context}) - - assert response = json_response(conn, 200) - assert response["phrase"] == filter.phrase - assert response["context"] == filter.context - assert response["irreversible"] == false - assert response["id"] != nil - assert response["id"] != "" - end - - test "fetching a list of filters", %{conn: conn} do - user = insert(:user) - - query_one = %Pleroma.Filter{ - user_id: user.id, - filter_id: 1, - phrase: "knights", - context: ["home"] - } - - query_two = %Pleroma.Filter{ - user_id: user.id, - filter_id: 2, - phrase: "who", - context: ["home"] - } - - {:ok, filter_one} = Pleroma.Filter.create(query_one) - {:ok, filter_two} = Pleroma.Filter.create(query_two) - - response = - conn - |> assign(:user, user) - |> get("/api/v1/filters") - |> json_response(200) - - assert response == - render_json( - FilterView, - "filters.json", - filters: [filter_two, filter_one] - ) - end - - test "get a filter", %{conn: conn} do - user = insert(:user) - - query = %Pleroma.Filter{ - user_id: user.id, - filter_id: 2, - phrase: "knight", - context: ["home"] - } - - {:ok, filter} = Pleroma.Filter.create(query) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/filters/#{filter.filter_id}") - - assert _response = json_response(conn, 200) - end - - test "update a filter", %{conn: conn} do - user = insert(:user) - - query = %Pleroma.Filter{ - user_id: user.id, - filter_id: 2, - phrase: "knight", - context: ["home"] - } - - {:ok, _filter} = Pleroma.Filter.create(query) - - new = %Pleroma.Filter{ - phrase: "nii", - context: ["home"] - } - - conn = - conn - |> assign(:user, user) - |> put("/api/v1/filters/#{query.filter_id}", %{ - phrase: new.phrase, - context: new.context - }) - - assert response = json_response(conn, 200) - assert response["phrase"] == new.phrase - assert response["context"] == new.context - end - - test "delete a filter", %{conn: conn} do - user = insert(:user) - - query = %Pleroma.Filter{ - user_id: user.id, - filter_id: 2, - phrase: "knight", - context: ["home"] - } - - {:ok, filter} = Pleroma.Filter.create(query) - - conn = - conn - |> assign(:user, user) - |> delete("/api/v1/filters/#{filter.filter_id}") - - assert response = json_response(conn, 200) - assert response == %{} - end - end - - describe "list timelines" do - test "list timeline", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - {:ok, _activity_one} = CommonAPI.post(user, %{"status" => "Marisa is cute."}) - {:ok, activity_two} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."}) - {:ok, list} = Pleroma.List.create("name", user) - {:ok, list} = Pleroma.List.follow(list, other_user) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/timelines/list/#{list.id}") - - assert [%{"id" => id}] = json_response(conn, 200) - - assert id == to_string(activity_two.id) - end - - test "list timeline does not leak non-public statuses for unfollowed users", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - {:ok, activity_one} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."}) - - {:ok, _activity_two} = - CommonAPI.post(other_user, %{ - "status" => "Marisa is cute.", - "visibility" => "private" - }) - - {:ok, list} = Pleroma.List.create("name", user) - {:ok, list} = Pleroma.List.follow(list, other_user) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/timelines/list/#{list.id}") - - assert [%{"id" => id}] = json_response(conn, 200) - - assert id == to_string(activity_one.id) - end - end - - describe "notifications" do - test "list of notifications", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) - - {:ok, [_notification]} = Notification.create_notifications(activity) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/notifications") - - expected_response = - "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{ - user.ap_id - }\">@<span>#{user.nickname}</span></a></span>" - - assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200) - assert response == expected_response - end - - test "getting a single notification", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) - - {:ok, [notification]} = Notification.create_notifications(activity) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/notifications/#{notification.id}") - - expected_response = - "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{ - user.ap_id - }\">@<span>#{user.nickname}</span></a></span>" - - assert %{"status" => %{"content" => response}} = json_response(conn, 200) - assert response == expected_response - end - - test "dismissing a single notification", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) - - {:ok, [notification]} = Notification.create_notifications(activity) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/notifications/dismiss", %{"id" => notification.id}) - - assert %{} = json_response(conn, 200) - end - - test "clearing all notifications", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) - - {:ok, [_notification]} = Notification.create_notifications(activity) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/notifications/clear") - - assert %{} = json_response(conn, 200) - - conn = - build_conn() - |> assign(:user, user) - |> get("/api/v1/notifications") - - assert all = json_response(conn, 200) - assert all == [] - end - - test "paginates notifications using min_id, since_id, max_id, and limit", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) - {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) - {:ok, activity3} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) - {:ok, activity4} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) - - notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string() - notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string() - notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string() - notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string() - - conn = - conn - |> assign(:user, user) - - # min_id - conn_res = - conn - |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}") - - result = json_response(conn_res, 200) - assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result - - # since_id - conn_res = - conn - |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}") - - result = json_response(conn_res, 200) - assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result - - # max_id - conn_res = - conn - |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}") - - result = json_response(conn_res, 200) - assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result - end - - test "filters notifications using exclude_types", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"}) - {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"}) - {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user) - {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user) - {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user) - - mention_notification_id = - Repo.get_by(Notification, activity_id: mention_activity.id).id |> to_string() - - favorite_notification_id = - Repo.get_by(Notification, activity_id: favorite_activity.id).id |> to_string() - - reblog_notification_id = - Repo.get_by(Notification, activity_id: reblog_activity.id).id |> to_string() - - follow_notification_id = - Repo.get_by(Notification, activity_id: follow_activity.id).id |> to_string() - - conn = - conn - |> assign(:user, user) - - conn_res = - get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]}) - - assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200) - - conn_res = - get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]}) - - assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200) - - conn_res = - get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]}) - - assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200) - - conn_res = - get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]}) - - assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200) - end - - test "destroy multiple", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) - {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) - {:ok, activity3} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"}) - {:ok, activity4} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"}) - - notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string() - notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string() - notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string() - notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string() - - conn = - conn - |> assign(:user, user) - - conn_res = - conn - |> get("/api/v1/notifications") - - result = json_response(conn_res, 200) - assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result - - conn2 = - conn - |> assign(:user, other_user) - - conn_res = - conn2 - |> get("/api/v1/notifications") - - result = json_response(conn_res, 200) - assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result - - conn_destroy = - conn - |> delete("/api/v1/notifications/destroy_multiple", %{ - "ids" => [notification1_id, notification2_id] - }) - - assert json_response(conn_destroy, 200) == %{} - - conn_res = - conn2 - |> get("/api/v1/notifications") - - result = json_response(conn_res, 200) - assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result - end - - test "doesn't see notifications after muting user with notifications", %{conn: conn} do - user = insert(:user) - user2 = insert(:user) - - {:ok, _, _, _} = CommonAPI.follow(user, user2) - {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"}) - - conn = assign(conn, :user, user) - - conn = get(conn, "/api/v1/notifications") - - assert length(json_response(conn, 200)) == 1 - - {:ok, user} = User.mute(user, user2) - - conn = assign(build_conn(), :user, user) - conn = get(conn, "/api/v1/notifications") - - assert json_response(conn, 200) == [] - end - - test "see notifications after muting user without notifications", %{conn: conn} do - user = insert(:user) - user2 = insert(:user) - - {:ok, _, _, _} = CommonAPI.follow(user, user2) - {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"}) - - conn = assign(conn, :user, user) - - conn = get(conn, "/api/v1/notifications") - - assert length(json_response(conn, 200)) == 1 - - {:ok, user} = User.mute(user, user2, false) - - conn = assign(build_conn(), :user, user) - conn = get(conn, "/api/v1/notifications") - - assert length(json_response(conn, 200)) == 1 - end - - test "see notifications after muting user with notifications and with_muted parameter", %{ - conn: conn - } do - user = insert(:user) - user2 = insert(:user) - - {:ok, _, _, _} = CommonAPI.follow(user, user2) - {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"}) - - conn = assign(conn, :user, user) - - conn = get(conn, "/api/v1/notifications") - - assert length(json_response(conn, 200)) == 1 - - {:ok, user} = User.mute(user, user2) - - conn = assign(build_conn(), :user, user) - conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"}) - - assert length(json_response(conn, 200)) == 1 - end - end - - describe "reblogging" do - test "reblogs and returns the reblogged status", %{conn: conn} do - activity = insert(:note_activity) - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/#{activity.id}/reblog") - - assert %{ - "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}, - "reblogged" => true - } = json_response(conn, 200) - - assert to_string(activity.id) == id - end - - test "reblogged status for another user", %{conn: conn} do - activity = insert(:note_activity) - user1 = insert(:user) - user2 = insert(:user) - user3 = insert(:user) - CommonAPI.favorite(activity.id, user2) - {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id) - {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1) - {:ok, _, _object} = CommonAPI.repeat(activity.id, user2) - - conn_res = - conn - |> assign(:user, user3) - |> get("/api/v1/statuses/#{reblog_activity1.id}") - - assert %{ - "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2}, - "reblogged" => false, - "favourited" => false, - "bookmarked" => false - } = json_response(conn_res, 200) - - conn_res = - conn - |> assign(:user, user2) - |> get("/api/v1/statuses/#{reblog_activity1.id}") - - assert %{ - "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2}, - "reblogged" => true, - "favourited" => true, - "bookmarked" => true - } = json_response(conn_res, 200) - - assert to_string(activity.id) == id - end - - test "returns 400 error when activity is not exist", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/foo/reblog") - - assert json_response(conn, 400) == %{"error" => "Could not repeat"} - end - end - - describe "unreblogging" do - test "unreblogs and returns the unreblogged status", %{conn: conn} do - activity = insert(:note_activity) - user = insert(:user) - - {:ok, _, _} = CommonAPI.repeat(activity.id, user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/#{activity.id}/unreblog") - - assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200) - - assert to_string(activity.id) == id - end - - test "returns 400 error when activity is not exist", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/foo/unreblog") - - assert json_response(conn, 400) == %{"error" => "Could not unrepeat"} - end - end - - describe "favoriting" do - test "favs a status and returns it", %{conn: conn} do - activity = insert(:note_activity) - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/#{activity.id}/favourite") - - assert %{"id" => id, "favourites_count" => 1, "favourited" => true} = - json_response(conn, 200) - - assert to_string(activity.id) == id - end - - test "returns 400 error for a wrong id", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/1/favourite") - - assert json_response(conn, 400) == %{"error" => "Could not favorite"} - end - end - - describe "unfavoriting" do - test "unfavorites a status and returns it", %{conn: conn} do - activity = insert(:note_activity) - user = insert(:user) - - {:ok, _, _} = CommonAPI.favorite(activity.id, user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/#{activity.id}/unfavourite") - - assert %{"id" => id, "favourites_count" => 0, "favourited" => false} = - json_response(conn, 200) - - assert to_string(activity.id) == id - end - - test "returns 400 error for a wrong id", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/1/unfavourite") - - assert json_response(conn, 400) == %{"error" => "Could not unfavorite"} - end - end - - describe "user timelines" do - test "gets a users statuses", %{conn: conn} do - user_one = insert(:user) - user_two = insert(:user) - user_three = insert(:user) - - {:ok, user_three} = User.follow(user_three, user_one) - - {:ok, activity} = CommonAPI.post(user_one, %{"status" => "HI!!!"}) - - {:ok, direct_activity} = - CommonAPI.post(user_one, %{ - "status" => "Hi, @#{user_two.nickname}.", - "visibility" => "direct" - }) - - {:ok, private_activity} = - CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"}) - - resp = - conn - |> get("/api/v1/accounts/#{user_one.id}/statuses") - - assert [%{"id" => id}] = json_response(resp, 200) - assert id == to_string(activity.id) - - resp = - conn - |> assign(:user, user_two) - |> get("/api/v1/accounts/#{user_one.id}/statuses") - - assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200) - assert id_one == to_string(direct_activity.id) - assert id_two == to_string(activity.id) - - resp = - conn - |> assign(:user, user_three) - |> get("/api/v1/accounts/#{user_one.id}/statuses") - - assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200) - assert id_one == to_string(private_activity.id) - assert id_two == to_string(activity.id) - end - - test "unimplemented pinned statuses feature", %{conn: conn} do - note = insert(:note_activity) - user = User.get_cached_by_ap_id(note.data["actor"]) - - conn = - conn - |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") - - assert json_response(conn, 200) == [] - end - - test "gets an users media", %{conn: conn} do - note = insert(:note_activity) - user = User.get_cached_by_ap_id(note.data["actor"]) - - file = %Plug.Upload{ - content_type: "image/jpg", - path: Path.absname("test/fixtures/image.jpg"), - filename: "an_image.jpg" - } - - {:ok, %{id: media_id}} = ActivityPub.upload(file, actor: user.ap_id) - - {:ok, image_post} = CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media_id]}) - - conn = - conn - |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"}) - - assert [%{"id" => id}] = json_response(conn, 200) - assert id == to_string(image_post.id) - - conn = - build_conn() - |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"}) - - assert [%{"id" => id}] = json_response(conn, 200) - assert id == to_string(image_post.id) - end - - test "gets a user's statuses without reblogs", %{conn: conn} do - user = insert(:user) - {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"}) - {:ok, _, _} = CommonAPI.repeat(post.id, user) - - conn = - conn - |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"}) - - assert [%{"id" => id}] = json_response(conn, 200) - assert id == to_string(post.id) - - conn = - conn - |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"}) - - assert [%{"id" => id}] = json_response(conn, 200) - assert id == to_string(post.id) - end - - test "filters user's statuses by a hashtag", %{conn: conn} do - user = insert(:user) - {:ok, post} = CommonAPI.post(user, %{"status" => "#hashtag"}) - {:ok, _post} = CommonAPI.post(user, %{"status" => "hashtag"}) - - conn = - conn - |> get("/api/v1/accounts/#{user.id}/statuses", %{"tagged" => "hashtag"}) - - assert [%{"id" => id}] = json_response(conn, 200) - assert id == to_string(post.id) - end - end - - describe "user relationships" do - test "returns the relationships for the current user", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - {:ok, user} = User.follow(user, other_user) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]}) - - assert [relationship] = json_response(conn, 200) - - assert to_string(other_user.id) == relationship["id"] - end - end - - describe "media upload" do - setup do - user = insert(:user) - - conn = - build_conn() - |> assign(:user, user) - - image = %Plug.Upload{ - content_type: "image/jpg", - path: Path.absname("test/fixtures/image.jpg"), - filename: "an_image.jpg" - } - - [conn: conn, image: image] - end - - clear_config([:media_proxy]) - clear_config([Pleroma.Upload]) - - test "returns uploaded image", %{conn: conn, image: image} do - desc = "Description of the image" - - media = - conn - |> post("/api/v1/media", %{"file" => image, "description" => desc}) - |> json_response(:ok) - - assert media["type"] == "image" - assert media["description"] == desc - assert media["id"] - - object = Repo.get(Object, media["id"]) - assert object.data["actor"] == User.ap_id(conn.assigns[:user]) - end - end - - describe "locked accounts" do - test "/api/v1/follow_requests works" do - user = insert(:user, %{info: %User.Info{locked: true}}) - other_user = insert(:user) - - {:ok, _activity} = ActivityPub.follow(other_user, user) - - user = User.get_cached_by_id(user.id) - other_user = User.get_cached_by_id(other_user.id) - - assert User.following?(other_user, user) == false - - conn = - build_conn() - |> assign(:user, user) - |> get("/api/v1/follow_requests") - - assert [relationship] = json_response(conn, 200) - assert to_string(other_user.id) == relationship["id"] - end - - test "/api/v1/follow_requests/:id/authorize works" do - user = insert(:user, %{info: %User.Info{locked: true}}) - other_user = insert(:user) - - {:ok, _activity} = ActivityPub.follow(other_user, user) - - user = User.get_cached_by_id(user.id) - other_user = User.get_cached_by_id(other_user.id) - - assert User.following?(other_user, user) == false - - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/follow_requests/#{other_user.id}/authorize") - - assert relationship = json_response(conn, 200) - assert to_string(other_user.id) == relationship["id"] - - user = User.get_cached_by_id(user.id) - other_user = User.get_cached_by_id(other_user.id) - - assert User.following?(other_user, user) == true - end - - test "verify_credentials", %{conn: conn} do - user = insert(:user, %{info: %User.Info{default_scope: "private"}}) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/verify_credentials") - - assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200) - assert id == to_string(user.id) - end - - test "/api/v1/follow_requests/:id/reject works" do - user = insert(:user, %{info: %User.Info{locked: true}}) - other_user = insert(:user) - - {:ok, _activity} = ActivityPub.follow(other_user, user) - - user = User.get_cached_by_id(user.id) - - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/follow_requests/#{other_user.id}/reject") - - assert relationship = json_response(conn, 200) - assert to_string(other_user.id) == relationship["id"] - - user = User.get_cached_by_id(user.id) - other_user = User.get_cached_by_id(other_user.id) - - assert User.following?(other_user, user) == false - end - end - - describe "account fetching" do - test "works by id" do - user = insert(:user) - - conn = - build_conn() - |> get("/api/v1/accounts/#{user.id}") - - assert %{"id" => id} = json_response(conn, 200) - assert id == to_string(user.id) - - conn = - build_conn() - |> get("/api/v1/accounts/-1") - - assert %{"error" => "Can't find user"} = json_response(conn, 404) - end - - test "works by nickname" do - user = insert(:user) - - conn = - build_conn() - |> get("/api/v1/accounts/#{user.nickname}") - - assert %{"id" => id} = json_response(conn, 200) - assert id == user.id - end - - test "works by nickname for remote users" do - limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content]) - Pleroma.Config.put([:instance, :limit_to_local_content], false) - user = insert(:user, nickname: "user@example.com", local: false) - - conn = - build_conn() - |> get("/api/v1/accounts/#{user.nickname}") - - Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local) - assert %{"id" => id} = json_response(conn, 200) - assert id == user.id - end - - test "respects limit_to_local_content == :all for remote user nicknames" do - limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content]) - Pleroma.Config.put([:instance, :limit_to_local_content], :all) - - user = insert(:user, nickname: "user@example.com", local: false) - - conn = - build_conn() - |> get("/api/v1/accounts/#{user.nickname}") - - Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local) - assert json_response(conn, 404) - end - - test "respects limit_to_local_content == :unauthenticated for remote user nicknames" do - limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content]) - Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated) - - user = insert(:user, nickname: "user@example.com", local: false) - reading_user = insert(:user) - - conn = - build_conn() - |> get("/api/v1/accounts/#{user.nickname}") - - assert json_response(conn, 404) - - conn = - build_conn() - |> assign(:user, reading_user) - |> get("/api/v1/accounts/#{user.nickname}") - - Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local) - assert %{"id" => id} = json_response(conn, 200) - assert id == user.id - end - end - - test "mascot upload", %{conn: conn} do - user = insert(:user) - - non_image_file = %Plug.Upload{ - content_type: "audio/mpeg", - path: Path.absname("test/fixtures/sound.mp3"), - filename: "sound.mp3" - } - - conn = - conn - |> assign(:user, user) - |> put("/api/v1/pleroma/mascot", %{"file" => non_image_file}) - - assert json_response(conn, 415) - - file = %Plug.Upload{ - content_type: "image/jpg", - path: Path.absname("test/fixtures/image.jpg"), - filename: "an_image.jpg" - } - - conn = - build_conn() - |> assign(:user, user) - |> put("/api/v1/pleroma/mascot", %{"file" => file}) - - assert %{"id" => _, "type" => image} = json_response(conn, 200) - end - - test "mascot retrieving", %{conn: conn} do - user = insert(:user) - # When user hasn't set a mascot, we should just get pleroma tan back - conn = - conn - |> assign(:user, user) - |> get("/api/v1/pleroma/mascot") - - assert %{"url" => url} = json_response(conn, 200) - assert url =~ "pleroma-fox-tan-smol" - - # When a user sets their mascot, we should get that back - file = %Plug.Upload{ - content_type: "image/jpg", - path: Path.absname("test/fixtures/image.jpg"), - filename: "an_image.jpg" - } - - conn = - build_conn() - |> assign(:user, user) - |> put("/api/v1/pleroma/mascot", %{"file" => file}) - - assert json_response(conn, 200) - - user = User.get_cached_by_id(user.id) - - conn = - build_conn() - |> assign(:user, user) - |> get("/api/v1/pleroma/mascot") - - assert %{"url" => url, "type" => "image"} = json_response(conn, 200) - assert url =~ "an_image" - end - - test "hashtag timeline", %{conn: conn} do - following = insert(:user) - - capture_log(fn -> - {:ok, activity} = CommonAPI.post(following, %{"status" => "test #2hu"}) - - {:ok, [_activity]} = - OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873") - - nconn = - conn - |> get("/api/v1/timelines/tag/2hu") - - assert [%{"id" => id}] = json_response(nconn, 200) - - assert id == to_string(activity.id) - - # works for different capitalization too - nconn = - conn - |> get("/api/v1/timelines/tag/2HU") - - assert [%{"id" => id}] = json_response(nconn, 200) - - assert id == to_string(activity.id) - end) - end - - test "multi-hashtag timeline", %{conn: conn} do - user = insert(:user) - - {:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"}) - {:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"}) - {:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"}) - - any_test = - conn - |> get("/api/v1/timelines/tag/test", %{"any" => ["test1"]}) - - [status_none, status_test1, status_test] = json_response(any_test, 200) - - assert to_string(activity_test.id) == status_test["id"] - assert to_string(activity_test1.id) == status_test1["id"] - assert to_string(activity_none.id) == status_none["id"] - - restricted_test = - conn - |> get("/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]}) - - assert [status_test1] == json_response(restricted_test, 200) - - all_test = conn |> get("/api/v1/timelines/tag/test", %{"all" => ["none"]}) - - assert [status_none] == json_response(all_test, 200) - end - - test "getting followers", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - {:ok, user} = User.follow(user, other_user) - - conn = - conn - |> get("/api/v1/accounts/#{other_user.id}/followers") - - assert [%{"id" => id}] = json_response(conn, 200) - assert id == to_string(user.id) - end - - test "getting followers, hide_followers", %{conn: conn} do - user = insert(:user) - other_user = insert(:user, %{info: %{hide_followers: true}}) - {:ok, _user} = User.follow(user, other_user) - - conn = - conn - |> get("/api/v1/accounts/#{other_user.id}/followers") - - assert [] == json_response(conn, 200) - end - - test "getting followers, hide_followers, same user requesting", %{conn: conn} do - user = insert(:user) - other_user = insert(:user, %{info: %{hide_followers: true}}) - {:ok, _user} = User.follow(user, other_user) - - conn = - conn - |> assign(:user, other_user) - |> get("/api/v1/accounts/#{other_user.id}/followers") - - refute [] == json_response(conn, 200) - end - - test "getting followers, pagination", %{conn: conn} do - user = insert(:user) - follower1 = insert(:user) - follower2 = insert(:user) - follower3 = insert(:user) - {:ok, _} = User.follow(follower1, user) - {:ok, _} = User.follow(follower2, user) - {:ok, _} = User.follow(follower3, user) - - conn = - conn - |> assign(:user, user) - - res_conn = - conn - |> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}") - - assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200) - assert id3 == follower3.id - assert id2 == follower2.id - - res_conn = - conn - |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}") - - assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200) - assert id2 == follower2.id - assert id1 == follower1.id - - res_conn = - conn - |> get("/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}") - - assert [%{"id" => id2}] = json_response(res_conn, 200) - assert id2 == follower2.id - - assert [link_header] = get_resp_header(res_conn, "link") - assert link_header =~ ~r/min_id=#{follower2.id}/ - assert link_header =~ ~r/max_id=#{follower2.id}/ - end - - test "getting following", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - {:ok, user} = User.follow(user, other_user) - - conn = - conn - |> get("/api/v1/accounts/#{user.id}/following") - - assert [%{"id" => id}] = json_response(conn, 200) - assert id == to_string(other_user.id) - end - - test "getting following, hide_follows", %{conn: conn} do - user = insert(:user, %{info: %{hide_follows: true}}) - other_user = insert(:user) - {:ok, user} = User.follow(user, other_user) - - conn = - conn - |> get("/api/v1/accounts/#{user.id}/following") - - assert [] == json_response(conn, 200) - end - - test "getting following, hide_follows, same user requesting", %{conn: conn} do - user = insert(:user, %{info: %{hide_follows: true}}) - other_user = insert(:user) - {:ok, user} = User.follow(user, other_user) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/#{user.id}/following") - - refute [] == json_response(conn, 200) - end - - test "getting following, pagination", %{conn: conn} do - user = insert(:user) - following1 = insert(:user) - following2 = insert(:user) - following3 = insert(:user) - {:ok, _} = User.follow(user, following1) - {:ok, _} = User.follow(user, following2) - {:ok, _} = User.follow(user, following3) - - conn = - conn - |> assign(:user, user) - - res_conn = - conn - |> get("/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}") - - assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200) - assert id3 == following3.id - assert id2 == following2.id - - res_conn = - conn - |> get("/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}") - - assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200) - assert id2 == following2.id - assert id1 == following1.id - - res_conn = - conn - |> get("/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}") - - assert [%{"id" => id2}] = json_response(res_conn, 200) - assert id2 == following2.id - - assert [link_header] = get_resp_header(res_conn, "link") - assert link_header =~ ~r/min_id=#{following2.id}/ - assert link_header =~ ~r/max_id=#{following2.id}/ - end - - test "following / unfollowing a user", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/follow") - - assert %{"id" => _id, "following" => true} = json_response(conn, 200) - - user = User.get_cached_by_id(user.id) - - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/unfollow") - - assert %{"id" => _id, "following" => false} = json_response(conn, 200) - - user = User.get_cached_by_id(user.id) - - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/follows", %{"uri" => other_user.nickname}) - - assert %{"id" => id} = json_response(conn, 200) - assert id == to_string(other_user.id) - end - - test "following without reblogs" do - follower = insert(:user) - followed = insert(:user) - other_user = insert(:user) - - conn = - build_conn() - |> assign(:user, follower) - |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=false") - - assert %{"showing_reblogs" => false} = json_response(conn, 200) - - {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"}) - {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed) - - conn = - build_conn() - |> assign(:user, User.get_cached_by_id(follower.id)) - |> get("/api/v1/timelines/home") - - assert [] == json_response(conn, 200) - - conn = - build_conn() - |> assign(:user, follower) - |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true") - - assert %{"showing_reblogs" => true} = json_response(conn, 200) - - conn = - build_conn() - |> assign(:user, User.get_cached_by_id(follower.id)) - |> get("/api/v1/timelines/home") - - expected_activity_id = reblog.id - assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200) - end - - test "following / unfollowing errors" do - user = insert(:user) - - conn = - build_conn() - |> assign(:user, user) - - # self follow - conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow") - assert %{"error" => "Record not found"} = json_response(conn_res, 404) - - # self unfollow - user = User.get_cached_by_id(user.id) - conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow") - assert %{"error" => "Record not found"} = json_response(conn_res, 404) - - # self follow via uri - user = User.get_cached_by_id(user.id) - conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname}) - assert %{"error" => "Record not found"} = json_response(conn_res, 404) - - # follow non existing user - conn_res = post(conn, "/api/v1/accounts/doesntexist/follow") - assert %{"error" => "Record not found"} = json_response(conn_res, 404) - - # follow non existing user via uri - conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"}) - assert %{"error" => "Record not found"} = json_response(conn_res, 404) - - # unfollow non existing user - conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow") - assert %{"error" => "Record not found"} = json_response(conn_res, 404) - end - - describe "mute/unmute" do - test "with notifications", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/mute") - - response = json_response(conn, 200) - - assert %{"id" => _id, "muting" => true, "muting_notifications" => true} = response - user = User.get_cached_by_id(user.id) - - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/unmute") - - response = json_response(conn, 200) - assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response - end - - test "without notifications", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"}) - - response = json_response(conn, 200) - - assert %{"id" => _id, "muting" => true, "muting_notifications" => false} = response - user = User.get_cached_by_id(user.id) - - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/unmute") - - response = json_response(conn, 200) - assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response - end - end - - test "subscribing / unsubscribing to a user", %{conn: conn} do - user = insert(:user) - subscription_target = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe") - - assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200) - - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe") - - assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200) - end - - test "getting a list of mutes", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, user} = User.mute(user, other_user) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/mutes") - - other_user_id = to_string(other_user.id) - assert [%{"id" => ^other_user_id}] = json_response(conn, 200) - end - - test "blocking / unblocking a user", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/block") - - assert %{"id" => _id, "blocking" => true} = json_response(conn, 200) - - user = User.get_cached_by_id(user.id) - - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/unblock") - - assert %{"id" => _id, "blocking" => false} = json_response(conn, 200) - end - - test "getting a list of blocks", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, user} = User.block(user, other_user) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/blocks") - - other_user_id = to_string(other_user.id) - assert [%{"id" => ^other_user_id}] = json_response(conn, 200) - end - - test "blocking / unblocking a domain", %{conn: conn} do - user = insert(:user) - other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"}) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) - - assert %{} = json_response(conn, 200) - user = User.get_cached_by_ap_id(user.ap_id) - assert User.blocks?(user, other_user) - - conn = - build_conn() - |> assign(:user, user) - |> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) - - assert %{} = json_response(conn, 200) - user = User.get_cached_by_ap_id(user.ap_id) - refute User.blocks?(user, other_user) - end - - test "getting a list of domain blocks", %{conn: conn} do - user = insert(:user) - - {:ok, user} = User.block_domain(user, "bad.site") - {:ok, user} = User.block_domain(user, "even.worse.site") - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/domain_blocks") - - domain_blocks = json_response(conn, 200) - - assert "bad.site" in domain_blocks - assert "even.worse.site" in domain_blocks - end - - test "unimplemented follow_requests, blocks, domain blocks" do - user = insert(:user) - - ["blocks", "domain_blocks", "follow_requests"] - |> Enum.each(fn endpoint -> - conn = - build_conn() - |> assign(:user, user) - |> get("/api/v1/#{endpoint}") - - assert [] = json_response(conn, 200) - end) - end - - test "returns the favorites of a user", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"}) - {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"}) - - {:ok, _, _} = CommonAPI.favorite(activity.id, user) - - first_conn = - conn - |> assign(:user, user) - |> get("/api/v1/favourites") - - assert [status] = json_response(first_conn, 200) - assert status["id"] == to_string(activity.id) - - assert [{"link", _link_header}] = - Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end) - - # Honours query params - {:ok, second_activity} = - CommonAPI.post(other_user, %{ - "status" => - "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful." - }) - - {:ok, _, _} = CommonAPI.favorite(second_activity.id, user) - - last_like = status["id"] - - second_conn = - conn - |> assign(:user, user) - |> get("/api/v1/favourites?since_id=#{last_like}") - - assert [second_status] = json_response(second_conn, 200) - assert second_status["id"] == to_string(second_activity.id) - - third_conn = - conn - |> assign(:user, user) - |> get("/api/v1/favourites?limit=0") - - assert [] = json_response(third_conn, 200) - end - - describe "getting favorites timeline of specified user" do - setup do - [current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}}) - [current_user: current_user, user: user] - end - - test "returns list of statuses favorited by specified user", %{ - conn: conn, - current_user: current_user, - user: user - } do - [activity | _] = insert_pair(:note_activity) - CommonAPI.favorite(activity.id, user) - - response = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) - - [like] = response - - assert length(response) == 1 - assert like["id"] == activity.id - end - - test "returns favorites for specified user_id when user is not logged in", %{ - conn: conn, - user: user - } do - activity = insert(:note_activity) - CommonAPI.favorite(activity.id, user) - - response = - conn - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) - - assert length(response) == 1 - end - - test "returns favorited DM only when user is logged in and he is one of recipients", %{ - conn: conn, - current_user: current_user, - user: user - } do - {:ok, direct} = - CommonAPI.post(current_user, %{ - "status" => "Hi @#{user.nickname}!", - "visibility" => "direct" - }) - - CommonAPI.favorite(direct.id, user) - - response = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) - - assert length(response) == 1 - - anonymous_response = - conn - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) - - assert Enum.empty?(anonymous_response) - end - - test "does not return others' favorited DM when user is not one of recipients", %{ - conn: conn, - current_user: current_user, - user: user - } do - user_two = insert(:user) - - {:ok, direct} = - CommonAPI.post(user_two, %{ - "status" => "Hi @#{user.nickname}!", - "visibility" => "direct" - }) - - CommonAPI.favorite(direct.id, user) - - response = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) - - assert Enum.empty?(response) - end - - test "paginates favorites using since_id and max_id", %{ - conn: conn, - current_user: current_user, - user: user - } do - activities = insert_list(10, :note_activity) - - Enum.each(activities, fn activity -> - CommonAPI.favorite(activity.id, user) - end) - - third_activity = Enum.at(activities, 2) - seventh_activity = Enum.at(activities, 6) - - response = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{ - since_id: third_activity.id, - max_id: seventh_activity.id - }) - |> json_response(:ok) - - assert length(response) == 3 - refute third_activity in response - refute seventh_activity in response - end - - test "limits favorites using limit parameter", %{ - conn: conn, - current_user: current_user, - user: user - } do - 7 - |> insert_list(:note_activity) - |> Enum.each(fn activity -> - CommonAPI.favorite(activity.id, user) - end) - - response = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"}) - |> json_response(:ok) - - assert length(response) == 3 - end - - test "returns empty response when user does not have any favorited statuses", %{ - conn: conn, - current_user: current_user, - user: user - } do - response = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) - - assert Enum.empty?(response) - end - - test "returns 404 error when specified user is not exist", %{conn: conn} do - conn = get(conn, "/api/v1/pleroma/accounts/test/favourites") - - assert json_response(conn, 404) == %{"error" => "Record not found"} - end - - test "returns 403 error when user has hidden own favorites", %{ - conn: conn, - current_user: current_user - } do - user = insert(:user, %{info: %{hide_favorites: true}}) - activity = insert(:note_activity) - CommonAPI.favorite(activity.id, user) - - conn = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - - assert json_response(conn, 403) == %{"error" => "Can't get favorites"} - end - - test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do - user = insert(:user) - activity = insert(:note_activity) - CommonAPI.favorite(activity.id, user) - - conn = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - - assert user.info.hide_favorites - assert json_response(conn, 403) == %{"error" => "Can't get favorites"} - end - end - - test "get instance information", %{conn: conn} do - conn = get(conn, "/api/v1/instance") - assert result = json_response(conn, 200) - - email = Config.get([:instance, :email]) - # Note: not checking for "max_toot_chars" since it's optional - assert %{ - "uri" => _, - "title" => _, - "description" => _, - "version" => _, - "email" => from_config_email, - "urls" => %{ - "streaming_api" => _ - }, - "stats" => _, - "thumbnail" => _, - "languages" => _, - "registrations" => _, - "poll_limits" => _ - } = result - - assert email == from_config_email - end - - test "get instance stats", %{conn: conn} do - user = insert(:user, %{local: true}) - - user2 = insert(:user, %{local: true}) - {:ok, _user2} = User.deactivate(user2, !user2.info.deactivated) - - insert(:user, %{local: false, nickname: "u@peer1.com"}) - insert(:user, %{local: false, nickname: "u@peer2.com"}) - - {:ok, _} = CommonAPI.post(user, %{"status" => "cofe"}) - - # Stats should count users with missing or nil `info.deactivated` value - user = User.get_cached_by_id(user.id) - info_change = Changeset.change(user.info, %{deactivated: nil}) - - {:ok, _user} = - user - |> Changeset.change() - |> Changeset.put_embed(:info, info_change) - |> User.update_and_set_cache() - - Pleroma.Stats.force_update() - - conn = get(conn, "/api/v1/instance") - - assert result = json_response(conn, 200) - - stats = result["stats"] - - assert stats - assert stats["user_count"] == 1 - assert stats["status_count"] == 1 - assert stats["domain_count"] == 2 - end - - test "get peers", %{conn: conn} do - insert(:user, %{local: false, nickname: "u@peer1.com"}) - insert(:user, %{local: false, nickname: "u@peer2.com"}) - - Pleroma.Stats.force_update() - - conn = get(conn, "/api/v1/instance/peers") - - assert result = json_response(conn, 200) - - assert ["peer1.com", "peer2.com"] == Enum.sort(result) - end - - test "put settings", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}}) - - assert _result = json_response(conn, 200) - - user = User.get_cached_by_ap_id(user.ap_id) - assert user.info.settings == %{"programming" => "socks"} - end - - describe "pinned statuses" do - setup do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"}) - - [user: user, activity: activity] - end - - clear_config([:instance, :max_pinned_statuses]) do - Config.put([:instance, :max_pinned_statuses], 1) - end - - test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do - {:ok, _} = CommonAPI.pin(activity.id, user) - - result = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") - |> json_response(200) - - id_str = to_string(activity.id) - - assert [%{"id" => ^id_str, "pinned" => true}] = result - end - - test "pin status", %{conn: conn, user: user, activity: activity} do - id_str = to_string(activity.id) - - assert %{"id" => ^id_str, "pinned" => true} = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/#{activity.id}/pin") - |> json_response(200) - - assert [%{"id" => ^id_str, "pinned" => true}] = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") - |> json_response(200) - end - - test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do - {:ok, dm} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"}) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/#{dm.id}/pin") - - assert json_response(conn, 400) == %{"error" => "Could not pin"} - end - - test "unpin status", %{conn: conn, user: user, activity: activity} do - {:ok, _} = CommonAPI.pin(activity.id, user) - - id_str = to_string(activity.id) - user = refresh_record(user) - - assert %{"id" => ^id_str, "pinned" => false} = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/#{activity.id}/unpin") - |> json_response(200) - - assert [] = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") - |> json_response(200) - end - - test "/unpin: returns 400 error when activity is not exist", %{conn: conn, user: user} do - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/1/unpin") - - assert json_response(conn, 400) == %{"error" => "Could not unpin"} - end - - test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do - {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"}) - - id_str_one = to_string(activity_one.id) - - assert %{"id" => ^id_str_one, "pinned" => true} = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/#{id_str_one}/pin") - |> json_response(200) - - user = refresh_record(user) - - assert %{"error" => "You have already pinned the maximum number of statuses"} = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/#{activity_two.id}/pin") - |> json_response(400) - end - end - - describe "cards" do - setup do - Config.put([:rich_media, :enabled], true) - - user = insert(:user) - %{user: user} - end - - test "returns rich-media card", %{conn: conn, user: user} do - {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"}) - - card_data = %{ - "image" => "http://ia.media-imdb.com/images/rock.jpg", - "provider_name" => "example.com", - "provider_url" => "https://example.com", - "title" => "The Rock", - "type" => "link", - "url" => "https://example.com/ogp", - "description" => - "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.", - "pleroma" => %{ - "opengraph" => %{ - "image" => "http://ia.media-imdb.com/images/rock.jpg", - "title" => "The Rock", - "type" => "video.movie", - "url" => "https://example.com/ogp", - "description" => - "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer." - } - } - } - - response = - conn - |> get("/api/v1/statuses/#{activity.id}/card") - |> json_response(200) - - assert response == card_data - - # works with private posts - {:ok, activity} = - CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"}) - - response_two = - conn - |> assign(:user, user) - |> get("/api/v1/statuses/#{activity.id}/card") - |> json_response(200) - - assert response_two == card_data - end - - test "replaces missing description with an empty string", %{conn: conn, user: user} do - {:ok, activity} = - CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"}) - - response = - conn - |> get("/api/v1/statuses/#{activity.id}/card") - |> json_response(:ok) - - assert response == %{ - "type" => "link", - "title" => "Pleroma", - "description" => "", - "image" => nil, - "provider_name" => "example.com", - "provider_url" => "https://example.com", - "url" => "https://example.com/ogp-missing-data", - "pleroma" => %{ - "opengraph" => %{ - "title" => "Pleroma", - "type" => "website", - "url" => "https://example.com/ogp-missing-data" - } - } - } - end - end - - test "bookmarks" do - user = insert(:user) - for_user = insert(:user) - - {:ok, activity1} = - CommonAPI.post(user, %{ - "status" => "heweoo?" - }) - - {:ok, activity2} = - CommonAPI.post(user, %{ - "status" => "heweoo!" - }) - - response1 = - build_conn() - |> assign(:user, for_user) - |> post("/api/v1/statuses/#{activity1.id}/bookmark") - - assert json_response(response1, 200)["bookmarked"] == true - - response2 = - build_conn() - |> assign(:user, for_user) - |> post("/api/v1/statuses/#{activity2.id}/bookmark") - - assert json_response(response2, 200)["bookmarked"] == true - - bookmarks = - build_conn() - |> assign(:user, for_user) - |> get("/api/v1/bookmarks") - - assert [json_response(response2, 200), json_response(response1, 200)] == - json_response(bookmarks, 200) - - response1 = - build_conn() - |> assign(:user, for_user) - |> post("/api/v1/statuses/#{activity1.id}/unbookmark") - - assert json_response(response1, 200)["bookmarked"] == false - - bookmarks = - build_conn() - |> assign(:user, for_user) - |> get("/api/v1/bookmarks") - - assert [json_response(response2, 200)] == json_response(bookmarks, 200) - end - - describe "conversation muting" do - setup do - post_user = insert(:user) - user = insert(:user) - - {:ok, activity} = CommonAPI.post(post_user, %{"status" => "HIE"}) - - [user: user, activity: activity] - end - - test "mute conversation", %{conn: conn, user: user, activity: activity} do - id_str = to_string(activity.id) - - assert %{"id" => ^id_str, "muted" => true} = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/#{activity.id}/mute") - |> json_response(200) - end - - test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do - {:ok, _} = CommonAPI.add_mute(user, activity) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/#{activity.id}/mute") - - assert json_response(conn, 400) == %{"error" => "conversation is already muted"} - end - - test "unmute conversation", %{conn: conn, user: user, activity: activity} do - {:ok, _} = CommonAPI.add_mute(user, activity) - - id_str = to_string(activity.id) - user = refresh_record(user) - - assert %{"id" => ^id_str, "muted" => false} = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/#{activity.id}/unmute") - |> json_response(200) - end - end - - describe "reports" do - setup do - reporter = insert(:user) - target_user = insert(:user) - - {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"}) - - [reporter: reporter, target_user: target_user, activity: activity] - end - - test "submit a basic report", %{conn: conn, reporter: reporter, target_user: target_user} do - assert %{"action_taken" => false, "id" => _} = - conn - |> assign(:user, reporter) - |> post("/api/v1/reports", %{"account_id" => target_user.id}) - |> json_response(200) - end - - test "submit a report with statuses and comment", %{ - conn: conn, - reporter: reporter, - target_user: target_user, - activity: activity - } do - assert %{"action_taken" => false, "id" => _} = - conn - |> assign(:user, reporter) - |> post("/api/v1/reports", %{ - "account_id" => target_user.id, - "status_ids" => [activity.id], - "comment" => "bad status!", - "forward" => "false" - }) - |> json_response(200) - end - - test "account_id is required", %{ - conn: conn, - reporter: reporter, - activity: activity - } do - assert %{"error" => "Valid `account_id` required"} = - conn - |> assign(:user, reporter) - |> post("/api/v1/reports", %{"status_ids" => [activity.id]}) - |> json_response(400) - end - - test "comment must be up to the size specified in the config", %{ - conn: conn, - reporter: reporter, - target_user: target_user - } do - max_size = Config.get([:instance, :max_report_comment_size], 1000) - comment = String.pad_trailing("a", max_size + 1, "a") - - error = %{"error" => "Comment must be up to #{max_size} characters"} - - assert ^error = - conn - |> assign(:user, reporter) - |> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment}) - |> json_response(400) - end - - test "returns error when account is not exist", %{ - conn: conn, - reporter: reporter, - activity: activity - } do - conn = - conn - |> assign(:user, reporter) - |> post("/api/v1/reports", %{"status_ids" => [activity.id], "account_id" => "foo"}) - - assert json_response(conn, 400) == %{"error" => "Account not found"} - end - end - - describe "link headers" do - test "preserves parameters in link headers", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity1} = - CommonAPI.post(other_user, %{ - "status" => "hi @#{user.nickname}", - "visibility" => "public" - }) - - {:ok, activity2} = - CommonAPI.post(other_user, %{ - "status" => "hi @#{user.nickname}", - "visibility" => "public" - }) - - notification1 = Repo.get_by(Notification, activity_id: activity1.id) - notification2 = Repo.get_by(Notification, activity_id: activity2.id) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/notifications", %{media_only: true}) - - assert [link_header] = get_resp_header(conn, "link") - assert link_header =~ ~r/media_only=true/ - assert link_header =~ ~r/min_id=#{notification2.id}/ - assert link_header =~ ~r/max_id=#{notification1.id}/ - end - end - - test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do - # Need to set an old-style integer ID to reproduce the problem - # (these are no longer assigned to new accounts but were preserved - # for existing accounts during the migration to flakeIDs) - user_one = insert(:user, %{id: 1212}) - user_two = insert(:user, %{nickname: "#{user_one.id}garbage"}) - - resp_one = - conn - |> get("/api/v1/accounts/#{user_one.id}") - - resp_two = - conn - |> get("/api/v1/accounts/#{user_two.nickname}") - - resp_three = - conn - |> get("/api/v1/accounts/#{user_two.id}") - - acc_one = json_response(resp_one, 200) - acc_two = json_response(resp_two, 200) - acc_three = json_response(resp_three, 200) - refute acc_one == acc_two - assert acc_two == acc_three - end - - describe "custom emoji" do - test "with tags", %{conn: conn} do - [emoji | _body] = - conn - |> get("/api/v1/custom_emojis") - |> json_response(200) - - assert Map.has_key?(emoji, "shortcode") - assert Map.has_key?(emoji, "static_url") - assert Map.has_key?(emoji, "tags") - assert is_list(emoji["tags"]) - assert Map.has_key?(emoji, "category") - assert Map.has_key?(emoji, "url") - assert Map.has_key?(emoji, "visible_in_picker") - end - end - - describe "index/2 redirections" do - setup %{conn: conn} do - session_opts = [ - store: :cookie, - key: "_test", - signing_salt: "cooldude" - ] - - conn = - conn - |> Plug.Session.call(Plug.Session.init(session_opts)) - |> fetch_session() - - test_path = "/web/statuses/test" - %{conn: conn, path: test_path} - end - - test "redirects not logged-in users to the login page", %{conn: conn, path: path} do - conn = get(conn, path) - - assert conn.status == 302 - assert redirected_to(conn) == "/web/login" - end - - test "redirects not logged-in users to the login page on private instances", %{ - conn: conn, - path: path - } do - Config.put([:instance, :public], false) - - conn = get(conn, path) - - assert conn.status == 302 - assert redirected_to(conn) == "/web/login" - end - - test "does not redirect logged in users to the login page", %{conn: conn, path: path} do - token = insert(:oauth_token) - - conn = - conn - |> assign(:user, token.user) - |> put_session(:oauth_token, token.token) - |> get(path) - - assert conn.status == 200 - end - - test "saves referer path to session", %{conn: conn, path: path} do - conn = get(conn, path) - return_to = Plug.Conn.get_session(conn, :return_to) - - assert return_to == path - end - - test "redirects to the saved path after log in", %{conn: conn, path: path} do - app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") - auth = insert(:oauth_authorization, app: app) - - conn = - conn - |> put_session(:return_to, path) - |> get("/web/login", %{code: auth.token}) - - assert conn.status == 302 - assert redirected_to(conn) == path - end - - test "redirects to the getting-started page when referer is not present", %{conn: conn} do - app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") - auth = insert(:oauth_authorization, app: app) - - conn = get(conn, "/web/login", %{code: auth.token}) - - assert conn.status == 302 - assert redirected_to(conn) == "/web/getting-started" - end - end - - describe "scheduled activities" do - test "creates a scheduled activity", %{conn: conn} do - user = insert(:user) - scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{ - "status" => "scheduled", - "scheduled_at" => scheduled_at - }) - - assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200) - assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at) - assert [] == Repo.all(Activity) - end - - test "creates a scheduled activity with a media attachment", %{conn: conn} do - user = insert(:user) - scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) - - file = %Plug.Upload{ - content_type: "image/jpg", - path: Path.absname("test/fixtures/image.jpg"), - filename: "an_image.jpg" - } - - {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{ - "media_ids" => [to_string(upload.id)], - "status" => "scheduled", - "scheduled_at" => scheduled_at - }) - - assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200) - assert %{"type" => "image"} = media_attachment - end - - test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now", - %{conn: conn} do - user = insert(:user) - - scheduled_at = - NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{ - "status" => "not scheduled", - "scheduled_at" => scheduled_at - }) - - assert %{"content" => "not scheduled"} = json_response(conn, 200) - assert [] == Repo.all(ScheduledActivity) - end - - test "returns error when daily user limit is exceeded", %{conn: conn} do - user = insert(:user) - - today = - NaiveDateTime.utc_now() - |> NaiveDateTime.add(:timer.minutes(6), :millisecond) - |> NaiveDateTime.to_iso8601() - - attrs = %{params: %{}, scheduled_at: today} - {:ok, _} = ScheduledActivity.create(user, attrs) - {:ok, _} = ScheduledActivity.create(user, attrs) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today}) - - assert %{"error" => "daily limit exceeded"} == json_response(conn, 422) - end - - test "returns error when total user limit is exceeded", %{conn: conn} do - user = insert(:user) - - today = - NaiveDateTime.utc_now() - |> NaiveDateTime.add(:timer.minutes(6), :millisecond) - |> NaiveDateTime.to_iso8601() - - tomorrow = - NaiveDateTime.utc_now() - |> NaiveDateTime.add(:timer.hours(36), :millisecond) - |> NaiveDateTime.to_iso8601() - - attrs = %{params: %{}, scheduled_at: today} - {:ok, _} = ScheduledActivity.create(user, attrs) - {:ok, _} = ScheduledActivity.create(user, attrs) - {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow}) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow}) - - assert %{"error" => "total limit exceeded"} == json_response(conn, 422) - end - - test "shows scheduled activities", %{conn: conn} do - user = insert(:user) - scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string() - scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string() - scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string() - scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string() - - conn = - conn - |> assign(:user, user) - - # min_id - conn_res = - conn - |> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}") - - result = json_response(conn_res, 200) - assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result - - # since_id - conn_res = - conn - |> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}") - - result = json_response(conn_res, 200) - assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result - - # max_id - conn_res = - conn - |> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}") - - result = json_response(conn_res, 200) - assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result - end - - test "shows a scheduled activity", %{conn: conn} do - user = insert(:user) - scheduled_activity = insert(:scheduled_activity, user: user) - - res_conn = - conn - |> assign(:user, user) - |> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}") - - assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200) - assert scheduled_activity_id == scheduled_activity.id |> to_string() - - res_conn = - conn - |> assign(:user, user) - |> get("/api/v1/scheduled_statuses/404") - - assert %{"error" => "Record not found"} = json_response(res_conn, 404) - end - - test "updates a scheduled activity", %{conn: conn} do - user = insert(:user) - scheduled_activity = insert(:scheduled_activity, user: user) - - new_scheduled_at = - NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) - - res_conn = - conn - |> assign(:user, user) - |> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{ - scheduled_at: new_scheduled_at - }) - - assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200) - assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at) - - res_conn = - conn - |> assign(:user, user) - |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at}) - - assert %{"error" => "Record not found"} = json_response(res_conn, 404) - end - - test "deletes a scheduled activity", %{conn: conn} do - user = insert(:user) - scheduled_activity = insert(:scheduled_activity, user: user) - - res_conn = - conn - |> assign(:user, user) - |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") - - assert %{} = json_response(res_conn, 200) - assert nil == Repo.get(ScheduledActivity, scheduled_activity.id) - - res_conn = - conn - |> assign(:user, user) - |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") - - assert %{"error" => "Record not found"} = json_response(res_conn, 404) - end - end - - test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do - user1 = insert(:user) - user2 = insert(:user) - user3 = insert(:user) - - {:ok, replied_to} = CommonAPI.post(user1, %{"status" => "cofe"}) - - # Reply to status from another user - conn1 = - conn - |> assign(:user, user2) - |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id}) - - assert %{"content" => "xD", "id" => id} = json_response(conn1, 200) - - activity = Activity.get_by_id_with_object(id) - - assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"] - assert Activity.get_in_reply_to_activity(activity).id == replied_to.id - - # Reblog from the third user - conn2 = - conn - |> assign(:user, user3) - |> post("/api/v1/statuses/#{activity.id}/reblog") - - assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} = - json_response(conn2, 200) - - assert to_string(activity.id) == id - - # Getting third user status - conn3 = - conn - |> assign(:user, user3) - |> get("api/v1/timelines/home") - - [reblogged_activity] = json_response(conn3, 200) - - assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id - - replied_to_user = User.get_by_ap_id(replied_to.data["actor"]) - assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id - end - - describe "create account by app" do - test "Account registration via Application", %{conn: conn} do - conn = - conn - |> post("/api/v1/apps", %{ - client_name: "client_name", - redirect_uris: "urn:ietf:wg:oauth:2.0:oob", - scopes: "read, write, follow" - }) - - %{ - "client_id" => client_id, - "client_secret" => client_secret, - "id" => _, - "name" => "client_name", - "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob", - "vapid_key" => _, - "website" => nil - } = json_response(conn, 200) - - conn = - conn - |> post("/oauth/token", %{ - grant_type: "client_credentials", - client_id: client_id, - client_secret: client_secret - }) - - assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} = - json_response(conn, 200) - - assert token - token_from_db = Repo.get_by(Token, token: token) - assert token_from_db - assert refresh - assert scope == "read write follow" - - conn = - build_conn() - |> put_req_header("authorization", "Bearer " <> token) - |> post("/api/v1/accounts", %{ - username: "lain", - email: "lain@example.org", - password: "PlzDontHackLain", - agreement: true - }) - - %{ - "access_token" => token, - "created_at" => _created_at, - "scope" => _scope, - "token_type" => "Bearer" - } = json_response(conn, 200) - - token_from_db = Repo.get_by(Token, token: token) - assert token_from_db - token_from_db = Repo.preload(token_from_db, :user) - assert token_from_db.user - - assert token_from_db.user.info.confirmation_pending - end - - test "rate limit", %{conn: conn} do - app_token = insert(:oauth_token, user: nil) - - conn = - put_req_header(conn, "authorization", "Bearer " <> app_token.token) - |> Map.put(:remote_ip, {15, 15, 15, 15}) - - for i <- 1..5 do - conn = - conn - |> post("/api/v1/accounts", %{ - username: "#{i}lain", - email: "#{i}lain@example.org", - password: "PlzDontHackLain", - agreement: true - }) - - %{ - "access_token" => token, - "created_at" => _created_at, - "scope" => _scope, - "token_type" => "Bearer" - } = json_response(conn, 200) - - token_from_db = Repo.get_by(Token, token: token) - assert token_from_db - token_from_db = Repo.preload(token_from_db, :user) - assert token_from_db.user - - assert token_from_db.user.info.confirmation_pending - end - - conn = - conn - |> post("/api/v1/accounts", %{ - username: "6lain", - email: "6lain@example.org", - password: "PlzDontHackLain", - agreement: true - }) - - assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"} - end - end - - describe "GET /api/v1/polls/:id" do - test "returns poll entity for object id", %{conn: conn} do - user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "Pleroma does", - "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20} - }) - - object = Object.normalize(activity) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/polls/#{object.id}") - - response = json_response(conn, 200) - id = to_string(object.id) - assert %{"id" => ^id, "expired" => false, "multiple" => false} = response - end - - test "does not expose polls for private statuses", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "Pleroma does", - "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20}, - "visibility" => "private" - }) - - object = Object.normalize(activity) - - conn = - conn - |> assign(:user, other_user) - |> get("/api/v1/polls/#{object.id}") - - assert json_response(conn, 404) - end - end - - describe "POST /api/v1/polls/:id/votes" do - test "votes are added to the poll", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "A very delicious sandwich", - "poll" => %{ - "options" => ["Lettuce", "Grilled Bacon", "Tomato"], - "expires_in" => 20, - "multiple" => true - } - }) - - object = Object.normalize(activity) - - conn = - conn - |> assign(:user, other_user) - |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]}) - - assert json_response(conn, 200) - object = Object.get_by_id(object.id) - - assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} -> - total_items == 1 - end) - end - - test "author can't vote", %{conn: conn} do - user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "Am I cute?", - "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20} - }) - - object = Object.normalize(activity) - - assert conn - |> assign(:user, user) - |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]}) - |> json_response(422) == %{"error" => "Poll's author can't vote"} - - object = Object.get_by_id(object.id) - - refute Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 1 - end - - test "does not allow multiple choices on a single-choice question", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "The glass is", - "poll" => %{"options" => ["half empty", "half full"], "expires_in" => 20} - }) - - object = Object.normalize(activity) - - assert conn - |> assign(:user, other_user) - |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]}) - |> json_response(422) == %{"error" => "Too many choices"} - - object = Object.get_by_id(object.id) - - refute Enum.any?(object.data["oneOf"], fn %{"replies" => %{"totalItems" => total_items}} -> - total_items == 1 - end) - end - - test "does not allow choice index to be greater than options count", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "Am I cute?", - "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20} - }) - - object = Object.normalize(activity) - - conn = - conn - |> assign(:user, other_user) - |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [2]}) - - assert json_response(conn, 422) == %{"error" => "Invalid indices"} - end - - test "returns 404 error when object is not exist", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/polls/1/votes", %{"choices" => [0]}) - - assert json_response(conn, 404) == %{"error" => "Record not found"} - end - - test "returns 404 when poll is private and not available for user", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "Am I cute?", - "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}, - "visibility" => "private" - }) - - object = Object.normalize(activity) - - conn = - conn - |> assign(:user, other_user) - |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0]}) - - assert json_response(conn, 404) == %{"error" => "Record not found"} - end - end - - describe "GET /api/v1/statuses/:id/favourited_by" do - setup do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "test"}) - - conn = - build_conn() - |> assign(:user, user) - - [conn: conn, activity: activity, user: user] - end - - test "returns users who have favorited the status", %{conn: conn, activity: activity} do - other_user = insert(:user) - {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) - - response = - conn - |> get("/api/v1/statuses/#{activity.id}/favourited_by") - |> json_response(:ok) - - [%{"id" => id}] = response - - assert id == other_user.id - end - - test "returns empty array when status has not been favorited yet", %{ - conn: conn, - activity: activity - } do - response = - conn - |> get("/api/v1/statuses/#{activity.id}/favourited_by") - |> json_response(:ok) - - assert Enum.empty?(response) - end - - test "does not return users who have favorited the status but are blocked", %{ - conn: %{assigns: %{user: user}} = conn, - activity: activity - } do - other_user = insert(:user) - {:ok, user} = User.block(user, other_user) - - {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) - - response = - conn - |> assign(:user, user) - |> get("/api/v1/statuses/#{activity.id}/favourited_by") - |> json_response(:ok) - - assert Enum.empty?(response) - end - - test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do - other_user = insert(:user) - {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) - - response = - conn - |> assign(:user, nil) - |> get("/api/v1/statuses/#{activity.id}/favourited_by") - |> json_response(:ok) - - [%{"id" => id}] = response - assert id == other_user.id - end - - test "requires authentification for private posts", %{conn: conn, user: user} do - other_user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "@#{other_user.nickname} wanna get some #cofe together?", - "visibility" => "direct" - }) - - {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) - - conn - |> assign(:user, nil) - |> get("/api/v1/statuses/#{activity.id}/favourited_by") - |> json_response(404) - - response = - build_conn() - |> assign(:user, other_user) - |> get("/api/v1/statuses/#{activity.id}/favourited_by") - |> json_response(200) - - [%{"id" => id}] = response - assert id == other_user.id - end - end - - describe "GET /api/v1/statuses/:id/reblogged_by" do - setup do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "test"}) - - conn = - build_conn() - |> assign(:user, user) - - [conn: conn, activity: activity, user: user] - end - - test "returns users who have reblogged the status", %{conn: conn, activity: activity} do - other_user = insert(:user) - {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) - - response = - conn - |> get("/api/v1/statuses/#{activity.id}/reblogged_by") - |> json_response(:ok) - - [%{"id" => id}] = response - - assert id == other_user.id - end - - test "returns empty array when status has not been reblogged yet", %{ - conn: conn, - activity: activity - } do - response = - conn - |> get("/api/v1/statuses/#{activity.id}/reblogged_by") - |> json_response(:ok) - - assert Enum.empty?(response) - end - - test "does not return users who have reblogged the status but are blocked", %{ - conn: %{assigns: %{user: user}} = conn, - activity: activity - } do - other_user = insert(:user) - {:ok, user} = User.block(user, other_user) - - {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) - - response = - conn - |> assign(:user, user) - |> get("/api/v1/statuses/#{activity.id}/reblogged_by") - |> json_response(:ok) - - assert Enum.empty?(response) - end - - test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do - other_user = insert(:user) - {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) - - response = - conn - |> assign(:user, nil) - |> get("/api/v1/statuses/#{activity.id}/reblogged_by") - |> json_response(:ok) - - [%{"id" => id}] = response - assert id == other_user.id - end - - test "requires authentification for private posts", %{conn: conn, user: user} do - other_user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "@#{other_user.nickname} wanna get some #cofe together?", - "visibility" => "direct" - }) - - conn - |> assign(:user, nil) - |> get("/api/v1/statuses/#{activity.id}/reblogged_by") - |> json_response(404) - - response = - build_conn() - |> assign(:user, other_user) - |> get("/api/v1/statuses/#{activity.id}/reblogged_by") - |> json_response(200) - - assert [] == response - end - end - - describe "POST /auth/password, with valid parameters" do - setup %{conn: conn} do - user = insert(:user) - conn = post(conn, "/auth/password?email=#{user.email}") - %{conn: conn, user: user} - end - - test "it returns 204", %{conn: conn} do - assert json_response(conn, :no_content) - end - - test "it creates a PasswordResetToken record for user", %{user: user} do - token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) - assert token_record - end - - test "it sends an email to user", %{user: user} do - token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) - - email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token) - notify_email = Config.get([:instance, :notify_email]) - instance_name = Config.get([:instance, :name]) - - assert_email_sent( - from: {instance_name, notify_email}, - to: {user.name, user.email}, - html_body: email.html_body - ) - end - end - - describe "POST /auth/password, with invalid parameters" do - setup do - user = insert(:user) - {:ok, user: user} - end - - test "it returns 404 when user is not found", %{conn: conn, user: user} do - conn = post(conn, "/auth/password?email=nonexisting_#{user.email}") - assert conn.status == 404 - assert conn.resp_body == "" - end - - test "it returns 400 when user is not local", %{conn: conn, user: user} do - {:ok, user} = Repo.update(Changeset.change(user, local: false)) - conn = post(conn, "/auth/password?email=#{user.email}") - assert conn.status == 400 - assert conn.resp_body == "" - end - end - - describe "POST /api/v1/pleroma/accounts/confirmation_resend" do - setup do - user = insert(:user) - info_change = User.Info.confirmation_changeset(user.info, need_confirmation: true) - - {:ok, user} = - user - |> Changeset.change() - |> Changeset.put_embed(:info, info_change) - |> Repo.update() - - assert user.info.confirmation_pending - - [user: user] - end - - clear_config([:instance, :account_activation_required]) do - Config.put([:instance, :account_activation_required], true) - end - - test "resend account confirmation email", %{conn: conn, user: user} do - conn - |> assign(:user, user) - |> post("/api/v1/pleroma/accounts/confirmation_resend?email=#{user.email}") - |> json_response(:no_content) - - email = Pleroma.Emails.UserEmail.account_confirmation_email(user) - notify_email = Config.get([:instance, :notify_email]) - instance_name = Config.get([:instance, :name]) - - assert_email_sent( - from: {instance_name, notify_email}, - to: {user.name, user.email}, - html_body: email.html_body - ) - end - end - - describe "GET /api/v1/suggestions" do - setup do - user = insert(:user) - other_user = insert(:user) - host = Config.get([Pleroma.Web.Endpoint, :url, :host]) - url500 = "http://test500?#{host}&#{user.nickname}" - url200 = "http://test200?#{host}&#{user.nickname}" - - mock(fn - %{method: :get, url: ^url500} -> - %Tesla.Env{status: 500, body: "bad request"} - - %{method: :get, url: ^url200} -> - %Tesla.Env{ - status: 200, - body: - ~s([{"acct":"yj455","avatar":"https://social.heldscal.la/avatar/201.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/201.jpeg"}, {"acct":"#{ - other_user.ap_id - }","avatar":"https://social.heldscal.la/avatar/202.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/202.jpeg"}]) - } - end) - - [user: user, other_user: other_user] - end - - clear_config(:suggestions) - - test "returns empty result when suggestions disabled", %{conn: conn, user: user} do - Config.put([:suggestions, :enabled], false) + describe "empty_array/2 (stubs)" do + test "GET /api/v1/accounts/:id/identity_proofs" do + %{user: user, conn: conn} = oauth_access(["n/a"]) res = conn |> assign(:user, user) - |> get("/api/v1/suggestions") + |> get("/api/v1/accounts/#{user.id}/identity_proofs") |> json_response(200) assert res == [] end - test "returns error", %{conn: conn, user: user} do - Config.put([:suggestions, :enabled], true) - Config.put([:suggestions, :third_party_engine], "http://test500?{{host}}&{{user}}") + test "GET /api/v1/endorsements" do + %{conn: conn} = oauth_access(["read:accounts"]) res = conn - |> assign(:user, user) - |> get("/api/v1/suggestions") - |> json_response(500) + |> get("/api/v1/endorsements") + |> json_response(200) - assert res == "Something went wrong" + assert res == [] end - test "returns suggestions", %{conn: conn, user: user, other_user: other_user} do - Config.put([:suggestions, :enabled], true) - Config.put([:suggestions, :third_party_engine], "http://test200?{{host}}&{{user}}") - + test "GET /api/v1/trends", %{conn: conn} do res = conn - |> assign(:user, user) - |> get("/api/v1/suggestions") + |> get("/api/v1/trends") |> json_response(200) - assert res == [ - %{ - "acct" => "yj455", - "avatar" => "https://social.heldscal.la/avatar/201.jpeg", - "avatar_static" => "https://social.heldscal.la/avatar/s/201.jpeg", - "id" => 0 - }, - %{ - "acct" => other_user.ap_id, - "avatar" => "https://social.heldscal.la/avatar/202.jpeg", - "avatar_static" => "https://social.heldscal.la/avatar/s/202.jpeg", - "id" => other_user.id - } - ] + assert res == [] end end end diff --git a/test/web/mastodon_api/mastodon_api_test.exs b/test/web/mastodon_api/mastodon_api_test.exs index 7fcb2bd55..cb971806a 100644 --- a/test/web/mastodon_api/mastodon_api_test.exs +++ b/test/web/mastodon_api/mastodon_api_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.MastodonAPITest do @@ -14,11 +14,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPITest do import Pleroma.Factory describe "follow/3" do - test "returns error when user deactivated" do + test "returns error when followed user is deactivated" do follower = insert(:user) - user = insert(:user, local: true, info: %{deactivated: true}) + user = insert(:user, local: true, deactivated: true) {:error, error} = MastodonAPI.follow(follower, user) - assert error == "Could not follow user: You are deactivated." + assert error == "Could not follow user: #{user.nickname} is deactivated." end test "following for user" do diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs index 1d8b28339..d60ed7b64 100644 --- a/test/web/mastodon_api/views/account_view_test.exs +++ b/test/web/mastodon_api/views/account_view_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.AccountViewTest do @@ -26,12 +26,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do user = insert(:user, %{ - info: %{ - note_count: 5, - follower_count: 3, - source_data: source_data, - background: background_image - }, + follower_count: 3, + note_count: 5, + source_data: source_data, + background: background_image, nickname: "shp@shitposter.club", name: ":karjalanpiirakka: shp", bio: "<script src=\"invalid-html\"></script><span>valid html</span>", @@ -67,7 +65,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do source: %{ note: "valid html", sensitive: false, - pleroma: %{}, + pleroma: %{ + actor_type: "Person", + discoverable: false + }, fields: [] }, pleroma: %{ @@ -79,36 +80,35 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do hide_favorites: true, hide_followers: false, hide_follows: false, + hide_followers_count: false, + hide_follows_count: false, relationship: %{}, skip_thread_containment: false } } - assert expected == AccountView.render("account.json", %{user: user}) + assert expected == AccountView.render("show.json", %{user: user}) end test "Represent the user account for the account owner" do user = insert(:user) - notification_settings = %{ - "followers" => true, - "follows" => true, - "non_follows" => true, - "non_followers" => true - } - - privacy = user.info.default_scope + notification_settings = %Pleroma.User.NotificationSetting{} + privacy = user.default_scope assert %{ - pleroma: %{notification_settings: ^notification_settings}, + pleroma: %{notification_settings: ^notification_settings, allow_following_move: true}, source: %{privacy: ^privacy} - } = AccountView.render("account.json", %{user: user, for: user}) + } = AccountView.render("show.json", %{user: user, for: user}) end test "Represent a Service(bot) account" do user = insert(:user, %{ - info: %{note_count: 5, follower_count: 3, source_data: %{"type" => "Service"}}, + follower_count: 3, + note_count: 5, + source_data: %{}, + actor_type: "Service", nickname: "shp@shitposter.club", inserted_at: ~N[2017-08-15 15:47:06.597036] }) @@ -135,7 +135,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do source: %{ note: user.bio, sensitive: false, - pleroma: %{}, + pleroma: %{ + actor_type: "Service", + discoverable: false + }, fields: [] }, pleroma: %{ @@ -147,18 +150,20 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do hide_favorites: true, hide_followers: false, hide_follows: false, + hide_followers_count: false, + hide_follows_count: false, relationship: %{}, skip_thread_containment: false } } - assert expected == AccountView.render("account.json", %{user: user}) + assert expected == AccountView.render("show.json", %{user: user}) end test "Represent a deactivated user for an admin" do - admin = insert(:user, %{info: %{is_admin: true}}) - deactivated_user = insert(:user, %{info: %{deactivated: true}}) - represented = AccountView.render("account.json", %{user: deactivated_user, for: admin}) + admin = insert(:user, is_admin: true) + deactivated_user = insert(:user, deactivated: true) + represented = AccountView.render("show.json", %{user: deactivated_user, for: admin}) assert represented[:pleroma][:deactivated] == true end @@ -182,9 +187,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do {:ok, user} = User.follow(user, other_user) {:ok, other_user} = User.follow(other_user, user) - {:ok, other_user} = User.subscribe(user, other_user) - {:ok, user} = User.mute(user, other_user, true) - {:ok, user} = CommonAPI.hide_reblogs(user, other_user) + {:ok, _subscription} = User.subscribe(user, other_user) + {:ok, _user_relationships} = User.mute(user, other_user, true) + {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, other_user) expected = %{ id: to_string(other_user.id), @@ -210,9 +215,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do other_user = insert(:user) {:ok, user} = User.follow(user, other_user) - {:ok, other_user} = User.subscribe(user, other_user) - {:ok, user} = User.block(user, other_user) - {:ok, other_user} = User.block(other_user, user) + {:ok, _subscription} = User.subscribe(user, other_user) + {:ok, _user_relationship} = User.block(user, other_user) + {:ok, _user_relationship} = User.block(other_user, user) expected = %{ id: to_string(other_user.id), @@ -245,7 +250,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do test "represent a relationship for the user with a pending follow request" do user = insert(:user) - other_user = insert(:user, %{info: %User.Info{locked: true}}) + other_user = insert(:user, locked: true) {:ok, user, other_user, _} = CommonAPI.follow(user, other_user) user = User.get_cached_by_id(user.id) @@ -274,14 +279,17 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do test "represent an embedded relationship" do user = insert(:user, %{ - info: %{note_count: 5, follower_count: 0, source_data: %{"type" => "Service"}}, + follower_count: 0, + note_count: 5, + source_data: %{}, + actor_type: "Service", nickname: "shp@shitposter.club", inserted_at: ~N[2017-08-15 15:47:06.597036] }) other_user = insert(:user) {:ok, other_user} = User.follow(other_user, user) - {:ok, other_user} = User.block(other_user, user) + {:ok, _user_relationship} = User.block(other_user, user) {:ok, _} = User.follow(insert(:user), user) expected = %{ @@ -306,7 +314,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do source: %{ note: user.bio, sensitive: false, - pleroma: %{}, + pleroma: %{ + actor_type: "Service", + discoverable: false + }, fields: [] }, pleroma: %{ @@ -318,6 +329,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do hide_favorites: true, hide_followers: false, hide_follows: false, + hide_followers_count: false, + hide_follows_count: false, relationship: %{ id: to_string(user.id), following: false, @@ -336,33 +349,49 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do } } - assert expected == AccountView.render("account.json", %{user: user, for: other_user}) + assert expected == + AccountView.render("show.json", %{user: refresh_record(user), for: other_user}) end test "returns the settings store if the requesting user is the represented user and it's requested specifically" do - user = insert(:user, %{info: %User.Info{pleroma_settings_store: %{fe: "test"}}}) + user = insert(:user, pleroma_settings_store: %{fe: "test"}) result = - AccountView.render("account.json", %{user: user, for: user, with_pleroma_settings: true}) + AccountView.render("show.json", %{user: user, for: user, with_pleroma_settings: true}) assert result.pleroma.settings_store == %{:fe => "test"} - result = AccountView.render("account.json", %{user: user, with_pleroma_settings: true}) + result = AccountView.render("show.json", %{user: user, with_pleroma_settings: true}) assert result.pleroma[:settings_store] == nil - result = AccountView.render("account.json", %{user: user, for: user}) + result = AccountView.render("show.json", %{user: user, for: user}) assert result.pleroma[:settings_store] == nil end - test "sanitizes display names" do + test "doesn't sanitize display names" do user = insert(:user, name: "<marquee> username </marquee>") - result = AccountView.render("account.json", %{user: user}) - refute result.display_name == "<marquee> username </marquee>" + result = AccountView.render("show.json", %{user: user}) + assert result.display_name == "<marquee> username </marquee>" + end + + test "never display nil user follow counts" do + user = insert(:user, following_count: 0, follower_count: 0) + result = AccountView.render("show.json", %{user: user}) + + assert result.following_count == 0 + assert result.followers_count == 0 end describe "hiding follows/following" do - test "shows when follows/following are hidden and sets follower/following count to 0" do - user = insert(:user, info: %{hide_followers: true, hide_follows: true}) + test "shows when follows/followers stats are hidden and sets follow/follower count to 0" do + user = + insert(:user, %{ + hide_followers: true, + hide_followers_count: true, + hide_follows: true, + hide_follows_count: true + }) + other_user = insert(:user) {:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user) {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) @@ -370,12 +399,25 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do assert %{ followers_count: 0, following_count: 0, + pleroma: %{hide_follows_count: true, hide_followers_count: true} + } = AccountView.render("show.json", %{user: user}) + end + + test "shows when follows/followers are hidden" do + user = insert(:user, hide_followers: true, hide_follows: true) + other_user = insert(:user) + {:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user) + {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) + + assert %{ + followers_count: 1, + following_count: 1, pleroma: %{hide_follows: true, hide_followers: true} - } = AccountView.render("account.json", %{user: user}) + } = AccountView.render("show.json", %{user: user}) end test "shows actual follower/following count to the account owner" do - user = insert(:user, info: %{hide_followers: true, hide_follows: true}) + user = insert(:user, hide_followers: true, hide_follows: true) other_user = insert(:user) {:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user) {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) @@ -383,7 +425,103 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do assert %{ followers_count: 1, following_count: 1 - } = AccountView.render("account.json", %{user: user, for: user}) + } = AccountView.render("show.json", %{user: user, for: user}) + end + + test "shows unread_conversation_count only to the account owner" do + user = insert(:user) + other_user = insert(:user) + + {:ok, _activity} = + CommonAPI.post(other_user, %{ + "status" => "Hey @#{user.nickname}.", + "visibility" => "direct" + }) + + user = User.get_cached_by_ap_id(user.ap_id) + + assert AccountView.render("show.json", %{user: user, for: other_user})[:pleroma][ + :unread_conversation_count + ] == nil + + assert AccountView.render("show.json", %{user: user, for: user})[:pleroma][ + :unread_conversation_count + ] == 1 + end + end + + describe "follow requests counter" do + test "shows zero when no follow requests are pending" do + user = insert(:user) + + assert %{follow_requests_count: 0} = + AccountView.render("show.json", %{user: user, for: user}) + + other_user = insert(:user) + {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) + + assert %{follow_requests_count: 0} = + AccountView.render("show.json", %{user: user, for: user}) + end + + test "shows non-zero when follow requests are pending" do + user = insert(:user, locked: true) + + assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user}) + + other_user = insert(:user) + {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) + + assert %{locked: true, follow_requests_count: 1} = + AccountView.render("show.json", %{user: user, for: user}) + end + + test "decreases when accepting a follow request" do + user = insert(:user, locked: true) + + assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user}) + + other_user = insert(:user) + {:ok, other_user, user, _activity} = CommonAPI.follow(other_user, user) + + assert %{locked: true, follow_requests_count: 1} = + AccountView.render("show.json", %{user: user, for: user}) + + {:ok, _other_user} = CommonAPI.accept_follow_request(other_user, user) + + assert %{locked: true, follow_requests_count: 0} = + AccountView.render("show.json", %{user: user, for: user}) + end + + test "decreases when rejecting a follow request" do + user = insert(:user, locked: true) + + assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user}) + + other_user = insert(:user) + {:ok, other_user, user, _activity} = CommonAPI.follow(other_user, user) + + assert %{locked: true, follow_requests_count: 1} = + AccountView.render("show.json", %{user: user, for: user}) + + {:ok, _other_user} = CommonAPI.reject_follow_request(other_user, user) + + assert %{locked: true, follow_requests_count: 0} = + AccountView.render("show.json", %{user: user, for: user}) + end + + test "shows non-zero when historical unapproved requests are present" do + user = insert(:user, locked: true) + + assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user}) + + other_user = insert(:user) + {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) + + {:ok, user} = User.update_and_set_cache(user, %{locked: false}) + + assert %{locked: false, follow_requests_count: 1} = + AccountView.render("show.json", %{user: user, for: user}) end end end diff --git a/test/web/mastodon_api/views/conversation_view_test.exs b/test/web/mastodon_api/views/conversation_view_test.exs index a2a880705..dbf3c51e2 100644 --- a/test/web/mastodon_api/views/conversation_view_test.exs +++ b/test/web/mastodon_api/views/conversation_view_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.ConversationViewTest do @@ -30,5 +30,6 @@ defmodule Pleroma.Web.MastodonAPI.ConversationViewTest do assert [account] = conversation.accounts assert account.id == other_user.id + assert conversation.last_status.pleroma.direct_conversation_id == participation.id end end diff --git a/test/web/mastodon_api/views/list_view_test.exs b/test/web/mastodon_api/views/list_view_test.exs index fb00310b9..ca99242cb 100644 --- a/test/web/mastodon_api/views/list_view_test.exs +++ b/test/web/mastodon_api/views/list_view_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.ListViewTest do diff --git a/test/web/mastodon_api/views/marker_view_test.exs b/test/web/mastodon_api/views/marker_view_test.exs new file mode 100644 index 000000000..893cf8857 --- /dev/null +++ b/test/web/mastodon_api/views/marker_view_test.exs @@ -0,0 +1,27 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.MarkerViewTest do + use Pleroma.DataCase + alias Pleroma.Web.MastodonAPI.MarkerView + import Pleroma.Factory + + test "returns markers" do + marker1 = insert(:marker, timeline: "notifications", last_read_id: "17") + marker2 = insert(:marker, timeline: "home", last_read_id: "42") + + assert MarkerView.render("markers.json", %{markers: [marker1, marker2]}) == %{ + "home" => %{ + last_read_id: "42", + updated_at: NaiveDateTime.to_iso8601(marker2.updated_at), + version: 0 + }, + "notifications" => %{ + last_read_id: "17", + updated_at: NaiveDateTime.to_iso8601(marker1.updated_at), + version: 0 + } + } + end +end diff --git a/test/web/mastodon_api/views/notification_view_test.exs b/test/web/mastodon_api/views/notification_view_test.exs index 8aed145d7..4df9c3c03 100644 --- a/test/web/mastodon_api/views/notification_view_test.exs +++ b/test/web/mastodon_api/views/notification_view_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do @@ -27,8 +27,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do id: to_string(notification.id), pleroma: %{is_seen: false}, type: "mention", - account: AccountView.render("account.json", %{user: user, for: mentioned_user}), - status: StatusView.render("status.json", %{activity: activity, for: mentioned_user}), + account: AccountView.render("show.json", %{user: user, for: mentioned_user}), + status: StatusView.render("show.json", %{activity: activity, for: mentioned_user}), created_at: Utils.to_masto_date(notification.inserted_at) } @@ -50,8 +50,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do id: to_string(notification.id), pleroma: %{is_seen: false}, type: "favourite", - account: AccountView.render("account.json", %{user: another_user, for: user}), - status: StatusView.render("status.json", %{activity: create_activity, for: user}), + account: AccountView.render("show.json", %{user: another_user, for: user}), + status: StatusView.render("show.json", %{activity: create_activity, for: user}), created_at: Utils.to_masto_date(notification.inserted_at) } @@ -72,8 +72,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do id: to_string(notification.id), pleroma: %{is_seen: false}, type: "reblog", - account: AccountView.render("account.json", %{user: another_user, for: user}), - status: StatusView.render("status.json", %{activity: reblog_activity, for: user}), + account: AccountView.render("show.json", %{user: another_user, for: user}), + status: StatusView.render("show.json", %{activity: reblog_activity, for: user}), created_at: Utils.to_masto_date(notification.inserted_at) } @@ -92,7 +92,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do id: to_string(notification.id), pleroma: %{is_seen: false}, type: "follow", - account: AccountView.render("account.json", %{user: follower, for: followed}), + account: AccountView.render("show.json", %{user: follower, for: followed}), created_at: Utils.to_masto_date(notification.inserted_at) } @@ -107,4 +107,58 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do assert [] == NotificationView.render("index.json", %{notifications: [notification], for: followed}) end + + test "Move notification" do + old_user = insert(:user) + new_user = insert(:user, also_known_as: [old_user.ap_id]) + follower = insert(:user) + + User.follow(follower, old_user) + Pleroma.Web.ActivityPub.ActivityPub.move(old_user, new_user) + Pleroma.Tests.ObanHelpers.perform_all() + + old_user = refresh_record(old_user) + new_user = refresh_record(new_user) + + [notification] = Notification.for_user(follower, %{with_move: true}) + + expected = %{ + id: to_string(notification.id), + pleroma: %{is_seen: false}, + type: "move", + account: AccountView.render("show.json", %{user: old_user, for: follower}), + target: AccountView.render("show.json", %{user: new_user, for: follower}), + created_at: Utils.to_masto_date(notification.inserted_at) + } + + assert [expected] == + NotificationView.render("index.json", %{notifications: [notification], for: follower}) + end + + test "EmojiReact notification" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) + {:ok, _activity, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") + + activity = Repo.get(Activity, activity.id) + + [notification] = Notification.for_user(user) + + assert notification + + expected = %{ + id: to_string(notification.id), + pleroma: %{is_seen: false}, + type: "pleroma:emoji_reaction", + emoji: "☕", + account: AccountView.render("show.json", %{user: other_user, for: user}), + status: StatusView.render("show.json", %{activity: activity, for: user}), + created_at: Utils.to_masto_date(notification.inserted_at) + } + + assert expected == + NotificationView.render("show.json", %{notification: notification, for: user}) + end end diff --git a/test/web/mastodon_api/views/poll_view_test.exs b/test/web/mastodon_api/views/poll_view_test.exs new file mode 100644 index 000000000..6211fa888 --- /dev/null +++ b/test/web/mastodon_api/views/poll_view_test.exs @@ -0,0 +1,126 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.PollViewTest do + use Pleroma.DataCase + + alias Pleroma.Object + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MastodonAPI.PollView + + import Pleroma.Factory + import Tesla.Mock + + setup do + mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + + test "renders a poll" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => "Is Tenshi eating a corndog cute?", + "poll" => %{ + "options" => ["absolutely!", "sure", "yes", "why are you even asking?"], + "expires_in" => 20 + } + }) + + object = Object.normalize(activity) + + expected = %{ + emojis: [], + expired: false, + id: to_string(object.id), + multiple: false, + options: [ + %{title: "absolutely!", votes_count: 0}, + %{title: "sure", votes_count: 0}, + %{title: "yes", votes_count: 0}, + %{title: "why are you even asking?", votes_count: 0} + ], + voted: false, + votes_count: 0 + } + + result = PollView.render("show.json", %{object: object}) + expires_at = result.expires_at + result = Map.delete(result, :expires_at) + + assert result == expected + + expires_at = NaiveDateTime.from_iso8601!(expires_at) + assert NaiveDateTime.diff(expires_at, NaiveDateTime.utc_now()) in 15..20 + end + + test "detects if it is multiple choice" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => "Which Mastodon developer is your favourite?", + "poll" => %{ + "options" => ["Gargron", "Eugen"], + "expires_in" => 20, + "multiple" => true + } + }) + + object = Object.normalize(activity) + + assert %{multiple: true} = PollView.render("show.json", %{object: object}) + end + + test "detects emoji" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => "What's with the smug face?", + "poll" => %{ + "options" => [":blank: sip", ":blank::blank: sip", ":blank::blank::blank: sip"], + "expires_in" => 20 + } + }) + + object = Object.normalize(activity) + + assert %{emojis: [%{shortcode: "blank"}]} = PollView.render("show.json", %{object: object}) + end + + test "detects vote status" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => "Which input devices do you use?", + "poll" => %{ + "options" => ["mouse", "trackball", "trackpoint"], + "multiple" => true, + "expires_in" => 20 + } + }) + + object = Object.normalize(activity) + + {:ok, _, object} = CommonAPI.vote(other_user, object, [1, 2]) + + result = PollView.render("show.json", %{object: object, for: other_user}) + + assert result[:voted] == true + assert Enum.at(result[:options], 1)[:votes_count] == 1 + assert Enum.at(result[:options], 2)[:votes_count] == 1 + end + + test "does not crash on polls with no end date" do + object = Object.normalize("https://skippers-bin.com/notes/7x9tmrp97i") + result = PollView.render("show.json", %{object: object}) + + assert result[:expires_at] == nil + assert result[:expired] == false + end +end diff --git a/test/web/mastodon_api/views/push_subscription_view_test.exs b/test/web/mastodon_api/views/push_subscription_view_test.exs index dc935fc82..10c6082a5 100644 --- a/test/web/mastodon_api/views/push_subscription_view_test.exs +++ b/test/web/mastodon_api/views/push_subscription_view_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.PushSubscriptionViewTest do diff --git a/test/web/mastodon_api/views/scheduled_activity_view_test.exs b/test/web/mastodon_api/views/scheduled_activity_view_test.exs index ecbb855d4..0c0987593 100644 --- a/test/web/mastodon_api/views/scheduled_activity_view_test.exs +++ b/test/web/mastodon_api/views/scheduled_activity_view_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.ScheduledActivityViewTest do diff --git a/test/web/mastodon_api/views/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs index fcdd7fbcb..191895c6f 100644 --- a/test/web/mastodon_api/views/status_view_test.exs +++ b/test/web/mastodon_api/views/status_view_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.StatusViewTest do @@ -7,6 +7,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do alias Pleroma.Activity alias Pleroma.Bookmark + alias Pleroma.Conversation.Participation + alias Pleroma.HTML alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User @@ -14,7 +16,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.StatusView - alias Pleroma.Web.OStatus import Pleroma.Factory import Tesla.Mock @@ -23,19 +24,64 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do :ok end - test "returns the direct conversation id when given the `with_conversation_id` option" do + test "has an emoji reaction list" do + user = insert(:user) + other_user = insert(:user) + third_user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "dae cofe??"}) + + {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, user, "☕") + {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, third_user, "🍵") + {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") + activity = Repo.get(Activity, activity.id) + status = StatusView.render("show.json", activity: activity) + + assert status[:pleroma][:emoji_reactions] == [ + %{name: "☕", count: 2, me: false}, + %{name: "🍵", count: 1, me: false} + ] + + status = StatusView.render("show.json", activity: activity, for: user) + + assert status[:pleroma][:emoji_reactions] == [ + %{name: "☕", count: 2, me: true}, + %{name: "🍵", count: 1, me: false} + ] + end + + test "loads and returns the direct conversation id when given the `with_direct_conversation_id` option" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"}) + [participation] = Participation.for_user(user) status = - StatusView.render("status.json", + StatusView.render("show.json", activity: activity, with_direct_conversation_id: true, for: user ) - assert status[:pleroma][:direct_conversation_id] + assert status[:pleroma][:direct_conversation_id] == participation.id + + status = StatusView.render("show.json", activity: activity, for: user) + assert status[:pleroma][:direct_conversation_id] == nil + end + + test "returns the direct conversation id when given the `direct_conversation_id` option" do + user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"}) + [participation] = Participation.for_user(user) + + status = + StatusView.render("show.json", + activity: activity, + direct_conversation_id: participation.id, + for: user + ) + + assert status[:pleroma][:direct_conversation_id] == participation.id end test "returns a temporary ap_id based user for activities missing db users" do @@ -46,7 +92,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do Repo.delete(user) Cachex.clear(:user_cache) - %{account: ms_user} = StatusView.render("status.json", activity: activity) + %{account: ms_user} = StatusView.render("show.json", activity: activity) assert ms_user.acct == "erroruser@example.com" end @@ -63,7 +109,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do Cachex.clear(:user_cache) - result = StatusView.render("status.json", activity: activity) + result = StatusView.render("show.json", activity: activity) assert result[:account][:id] == to_string(user.id) end @@ -81,7 +127,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do User.get_cached_by_ap_id(note.data["actor"]) - status = StatusView.render("status.json", %{activity: note}) + status = StatusView.render("show.json", %{activity: note}) assert status.content == "" end @@ -93,7 +139,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do convo_id = Utils.context_to_conversation_id(object_data["context"]) - status = StatusView.render("status.json", %{activity: note}) + status = StatusView.render("show.json", %{activity: note}) created_at = (object_data["published"] || "") @@ -103,12 +149,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do id: to_string(note.id), uri: object_data["id"], url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note), - account: AccountView.render("account.json", %{user: user}), + account: AccountView.render("show.json", %{user: user}), in_reply_to_id: nil, in_reply_to_account_id: nil, card: nil, reblog: nil, - content: HtmlSanitizeEx.basic_html(object_data["content"]), + content: HTML.filter_tags(object_data["content"]), created_at: created_at, reblogs_count: 0, replies_count: 0, @@ -120,7 +166,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do pinned: false, sensitive: false, poll: nil, - spoiler_text: HtmlSanitizeEx.basic_html(object_data["summary"]), + spoiler_text: HTML.filter_tags(object_data["summary"]), visibility: "public", media_attachments: [], mentions: [], @@ -147,11 +193,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do local: true, conversation_id: convo_id, in_reply_to_account_acct: nil, - content: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["content"])}, - spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["summary"])}, + content: %{"text/plain" => HTML.strip_tags(object_data["content"])}, + spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])}, expires_at: nil, direct_conversation_id: nil, - thread_muted: false + thread_muted: false, + emoji_reactions: [] } } @@ -162,14 +209,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do user = insert(:user) other_user = insert(:user) - {:ok, user} = User.mute(user, other_user) + {:ok, _user_relationships} = User.mute(user, other_user) {:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"}) - status = StatusView.render("status.json", %{activity: activity}) + status = StatusView.render("show.json", %{activity: activity}) assert status.muted == false - status = StatusView.render("status.json", %{activity: activity, for: user}) + status = StatusView.render("show.json", %{activity: activity, for: user}) assert status.muted == true end @@ -178,16 +225,16 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do user = insert(:user) other_user = insert(:user) - {:ok, user} = User.mute(user, other_user) + {:ok, _user_relationships} = User.mute(user, other_user) {:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"}) - status = StatusView.render("status.json", %{activity: activity, for: user}) + status = StatusView.render("show.json", %{activity: activity, for: user}) assert status.pleroma.thread_muted == false {:ok, activity} = CommonAPI.add_mute(user, activity) - status = StatusView.render("status.json", %{activity: activity, for: user}) + status = StatusView.render("show.json", %{activity: activity, for: user}) assert status.pleroma.thread_muted == true end @@ -196,11 +243,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "Cute girls doing cute things"}) - status = StatusView.render("status.json", %{activity: activity}) + status = StatusView.render("show.json", %{activity: activity}) assert status.bookmarked == false - status = StatusView.render("status.json", %{activity: activity, for: user}) + status = StatusView.render("show.json", %{activity: activity, for: user}) assert status.bookmarked == false @@ -208,7 +255,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do activity = Activity.get_by_id_with_object(activity.id) - status = StatusView.render("status.json", %{activity: activity, for: user}) + status = StatusView.render("show.json", %{activity: activity, for: user}) assert status.bookmarked == true end @@ -220,7 +267,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do {:ok, activity} = CommonAPI.post(user, %{"status" => "he", "in_reply_to_status_id" => note.id}) - status = StatusView.render("status.json", %{activity: activity}) + status = StatusView.render("show.json", %{activity: activity}) assert status.in_reply_to_id == to_string(note.id) @@ -230,17 +277,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do end test "contains mentions" do - incoming = File.read!("test/fixtures/incoming_reply_mastodon.xml") - # a user with this ap id might be in the cache. - recipient = "https://pleroma.soykaf.com/users/lain" - user = insert(:user, %{ap_id: recipient}) + user = insert(:user) + mentioned = insert(:user) - {:ok, [activity]} = OStatus.handle_incoming(incoming) + {:ok, activity} = CommonAPI.post(user, %{"status" => "hi @#{mentioned.nickname}"}) - status = StatusView.render("status.json", %{activity: activity}) + status = StatusView.render("show.json", %{activity: activity}) assert status.mentions == - Enum.map([user], fn u -> AccountView.render("mention.json", %{user: u}) end) + Enum.map([mentioned], fn u -> AccountView.render("mention.json", %{user: u}) end) end test "create mentions from the 'to' field" do @@ -263,7 +308,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do assert length(activity.recipients) == 3 - %{mentions: [mention] = mentions} = StatusView.render("status.json", %{activity: activity}) + %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity}) assert length(mentions) == 1 assert mention.url == recipient_ap_id @@ -300,7 +345,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do assert length(activity.recipients) == 3 - %{mentions: [mention] = mentions} = StatusView.render("status.json", %{activity: activity}) + %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity}) assert length(mentions) == 1 assert mention.url == recipient.ap_id @@ -340,7 +385,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810" [activity] = Activity.search(nil, id) - status = StatusView.render("status.json", %{activity: activity}) + status = StatusView.render("show.json", %{activity: activity}) assert status.uri == id assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/" @@ -352,7 +397,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do {:ok, reblog, _} = CommonAPI.repeat(activity.id, user) - represented = StatusView.render("status.json", %{for: user, activity: reblog}) + represented = StatusView.render("show.json", %{for: user, activity: reblog}) assert represented[:id] == to_string(reblog.id) assert represented[:reblog][:id] == to_string(activity.id) @@ -369,12 +414,27 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"]) - represented = StatusView.render("status.json", %{for: user, activity: activity}) + represented = StatusView.render("show.json", %{for: user, activity: activity}) assert represented[:id] == to_string(activity.id) assert length(represented[:media_attachments]) == 1 end + test "a Mobilizon event" do + user = insert(:user) + + {:ok, object} = + Pleroma.Object.Fetcher.fetch_object_from_id( + "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39" + ) + + %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"]) + + represented = StatusView.render("show.json", %{for: user, activity: activity}) + + assert represented[:id] == to_string(activity.id) + end + describe "build_tags/1" do test "it returns a a dictionary tags" do object_tags = [ @@ -431,7 +491,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do title: "Example website" } - %{provider_name: "Example site name"} = + %{provider_name: "example.com"} = StatusView.render("card.json", %{page_url: page_url, rich_media: card}) end @@ -446,121 +506,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do description: "Example description" } - %{provider_name: "Example site name"} = + %{provider_name: "example.com"} = StatusView.render("card.json", %{page_url: page_url, rich_media: card}) end end - describe "poll view" do - test "renders a poll" do - user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "Is Tenshi eating a corndog cute?", - "poll" => %{ - "options" => ["absolutely!", "sure", "yes", "why are you even asking?"], - "expires_in" => 20 - } - }) - - object = Object.normalize(activity) - - expected = %{ - emojis: [], - expired: false, - id: to_string(object.id), - multiple: false, - options: [ - %{title: "absolutely!", votes_count: 0}, - %{title: "sure", votes_count: 0}, - %{title: "yes", votes_count: 0}, - %{title: "why are you even asking?", votes_count: 0} - ], - voted: false, - votes_count: 0 - } - - result = StatusView.render("poll.json", %{object: object}) - expires_at = result.expires_at - result = Map.delete(result, :expires_at) - - assert result == expected - - expires_at = NaiveDateTime.from_iso8601!(expires_at) - assert NaiveDateTime.diff(expires_at, NaiveDateTime.utc_now()) in 15..20 - end - - test "detects if it is multiple choice" do - user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "Which Mastodon developer is your favourite?", - "poll" => %{ - "options" => ["Gargron", "Eugen"], - "expires_in" => 20, - "multiple" => true - } - }) - - object = Object.normalize(activity) - - assert %{multiple: true} = StatusView.render("poll.json", %{object: object}) - end - - test "detects emoji" do - user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "What's with the smug face?", - "poll" => %{ - "options" => [":blank: sip", ":blank::blank: sip", ":blank::blank::blank: sip"], - "expires_in" => 20 - } - }) - - object = Object.normalize(activity) - - assert %{emojis: [%{shortcode: "blank"}]} = - StatusView.render("poll.json", %{object: object}) - end - - test "detects vote status" do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "Which input devices do you use?", - "poll" => %{ - "options" => ["mouse", "trackball", "trackpoint"], - "multiple" => true, - "expires_in" => 20 - } - }) - - object = Object.normalize(activity) - - {:ok, _, object} = CommonAPI.vote(other_user, object, [1, 2]) - - result = StatusView.render("poll.json", %{object: object, for: other_user}) - - assert result[:voted] == true - assert Enum.at(result[:options], 1)[:votes_count] == 1 - assert Enum.at(result[:options], 2)[:votes_count] == 1 - end - - test "does not crash on polls with no end date" do - object = Object.normalize("https://skippers-bin.com/notes/7x9tmrp97i") - result = StatusView.render("poll.json", %{object: object}) - - assert result[:expires_at] == nil - assert result[:expired] == false - end - end - test "embeds a relationship in the account" do user = insert(:user) other_user = insert(:user) @@ -570,7 +520,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do "status" => "drink more water" }) - result = StatusView.render("status.json", %{activity: activity, for: other_user}) + result = StatusView.render("show.json", %{activity: activity, for: other_user}) assert result[:account][:pleroma][:relationship] == AccountView.render("relationship.json", %{user: other_user, target: user}) @@ -587,7 +537,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do {:ok, activity, _object} = CommonAPI.repeat(activity.id, other_user) - result = StatusView.render("status.json", %{activity: activity, for: user}) + result = StatusView.render("show.json", %{activity: activity, for: user}) assert result[:account][:pleroma][:relationship] == AccountView.render("relationship.json", %{user: user, target: other_user}) @@ -604,8 +554,17 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do {:ok, activity} = CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"}) - status = StatusView.render("status.json", activity: activity) + status = StatusView.render("show.json", activity: activity) assert status.visibility == "list" end + + test "successfully renders a Listen activity (pleroma extension)" do + listen_activity = insert(:listen) + + status = StatusView.render("listen.json", activity: listen_activity) + + assert status.length == listen_activity.data["object"]["length"] + assert status.title == listen_activity.data["object"]["title"] + end end diff --git a/test/web/media_proxy/media_proxy_controller_test.exs b/test/web/media_proxy/media_proxy_controller_test.exs index 53b8f556b..f035dfeee 100644 --- a/test/web/media_proxy/media_proxy_controller_test.exs +++ b/test/web/media_proxy/media_proxy_controller_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do @@ -7,11 +7,8 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do import Mock alias Pleroma.Config - setup do - media_proxy_config = Config.get([:media_proxy]) || [] - on_exit(fn -> Config.put([:media_proxy], media_proxy_config) end) - :ok - end + clear_config(:media_proxy) + clear_config([Pleroma.Web.Endpoint, :secret_key_base]) test "it returns 404 when MediaProxy disabled", %{conn: conn} do Config.put([:media_proxy, :enabled], false) diff --git a/test/web/media_proxy/media_proxy_test.exs b/test/web/media_proxy/media_proxy_test.exs index 79699cac5..8f5fcf2eb 100644 --- a/test/web/media_proxy/media_proxy_test.exs +++ b/test/web/media_proxy/media_proxy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MediaProxyTest do @@ -9,6 +9,7 @@ defmodule Pleroma.Web.MediaProxyTest do alias Pleroma.Web.MediaProxy.MediaProxyController clear_config([:media_proxy, :enabled]) + clear_config(Pleroma.Upload) describe "when enabled" do setup do @@ -224,7 +225,6 @@ defmodule Pleroma.Web.MediaProxyTest do end test "ensure Pleroma.Upload base_url is always whitelisted" do - upload_config = Pleroma.Config.get([Pleroma.Upload]) media_url = "https://media.pleroma.social" Pleroma.Config.put([Pleroma.Upload, :base_url], media_url) @@ -232,8 +232,6 @@ defmodule Pleroma.Web.MediaProxyTest do encoded = url(url) assert String.starts_with?(encoded, media_url) - - Pleroma.Config.put([Pleroma.Upload], upload_config) end end end diff --git a/test/web/metadata/feed_test.exs b/test/web/metadata/feed_test.exs new file mode 100644 index 000000000..e6e5cc5ed --- /dev/null +++ b/test/web/metadata/feed_test.exs @@ -0,0 +1,18 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Metadata.Providers.FeedTest do + use Pleroma.DataCase + import Pleroma.Factory + alias Pleroma.Web.Metadata.Providers.Feed + + test "it renders a link to user's atom feed" do + user = insert(:user, nickname: "lain") + + assert Feed.build_tags(%{user: user}) == [ + {:link, + [rel: "alternate", type: "application/atom+xml", href: "/users/lain/feed.atom"], []} + ] + end +end diff --git a/test/web/metadata/opengraph_test.exs b/test/web/metadata/opengraph_test.exs index 4283f72cd..9d7c009eb 100644 --- a/test/web/metadata/opengraph_test.exs +++ b/test/web/metadata/opengraph_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Metadata.Providers.OpenGraphTest do @@ -7,6 +7,8 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraphTest do import Pleroma.Factory alias Pleroma.Web.Metadata.Providers.OpenGraph + clear_config([Pleroma.Web.Metadata, :unfurl_nsfw]) + test "it renders all supported types of attachments and skips unknown types" do user = insert(:user) diff --git a/test/web/metadata/player_view_test.exs b/test/web/metadata/player_view_test.exs index 742b0ed8b..e6c990242 100644 --- a/test/web/metadata/player_view_test.exs +++ b/test/web/metadata/player_view_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Metadata.PlayerViewTest do diff --git a/test/web/metadata/rel_me_test.exs b/test/web/metadata/rel_me_test.exs index 3874e077b..4107a8459 100644 --- a/test/web/metadata/rel_me_test.exs +++ b/test/web/metadata/rel_me_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Metadata.Providers.RelMeTest do diff --git a/test/web/metadata/twitter_card_test.exs b/test/web/metadata/twitter_card_test.exs index 0814006d2..3d75d1ed5 100644 --- a/test/web/metadata/twitter_card_test.exs +++ b/test/web/metadata/twitter_card_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Metadata.Providers.TwitterCardTest do @@ -13,6 +13,8 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCardTest do alias Pleroma.Web.Metadata.Utils alias Pleroma.Web.Router + clear_config([Pleroma.Web.Metadata, :unfurl_nsfw]) + test "it renders twitter card for user info" do user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994") avatar_url = Utils.attachment_url(User.avatar_url(user)) @@ -26,7 +28,32 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCardTest do ] end - test "it does not render attachments if post is nsfw" do + test "it uses summary twittercard if post has no attachment" do + user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994") + {:ok, activity} = CommonAPI.post(user, %{"status" => "HI"}) + + note = + insert(:note, %{ + data: %{ + "actor" => user.ap_id, + "tag" => [], + "id" => "https://pleroma.gov/objects/whatever", + "content" => "pleroma in a nutshell" + } + }) + + result = TwitterCard.build_tags(%{object: note, user: user, activity_id: activity.id}) + + assert [ + {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []}, + {:meta, [property: "twitter:description", content: "“pleroma in a nutshell”"], []}, + {:meta, [property: "twitter:image", content: "http://localhost:4001/images/avi.png"], + []}, + {:meta, [property: "twitter:card", content: "summary"], []} + ] == result + end + + test "it renders avatar not attachment if post is nsfw and unfurl_nsfw is disabled" do Pleroma.Config.put([Pleroma.Web.Metadata, :unfurl_nsfw], false) user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994") {:ok, activity} = CommonAPI.post(user, %{"status" => "HI"}) @@ -67,7 +94,7 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCardTest do {:meta, [property: "twitter:description", content: "“pleroma in a nutshell”"], []}, {:meta, [property: "twitter:image", content: "http://localhost:4001/images/avi.png"], []}, - {:meta, [property: "twitter:card", content: "summary_large_image"], []} + {:meta, [property: "twitter:card", content: "summary"], []} ] == result end diff --git a/test/web/metadata/utils_test.exs b/test/web/metadata/utils_test.exs index 7547f2932..8183256d8 100644 --- a/test/web/metadata/utils_test.exs +++ b/test/web/metadata/utils_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Metadata.UtilsTest do diff --git a/test/web/mongooseim/mongoose_im_controller_test.exs b/test/web/mongooseim/mongoose_im_controller_test.exs index eb83999bb..291ae54fc 100644 --- a/test/web/mongooseim/mongoose_im_controller_test.exs +++ b/test/web/mongooseim/mongoose_im_controller_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MongooseIMController do diff --git a/test/web/node_info_test.exs b/test/web/node_info_test.exs index 4edcdaf60..ee10ad5db 100644 --- a/test/web/node_info_test.exs +++ b/test/web/node_info_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.NodeInfoTest do @@ -7,6 +7,9 @@ defmodule Pleroma.Web.NodeInfoTest do import Pleroma.Factory + clear_config([:mrf_simple]) + clear_config(:instance) + test "GET /.well-known/nodeinfo", %{conn: conn} do links = conn @@ -24,8 +27,8 @@ defmodule Pleroma.Web.NodeInfoTest do end test "nodeinfo shows staff accounts", %{conn: conn} do - moderator = insert(:user, %{local: true, info: %{is_moderator: true}}) - admin = insert(:user, %{local: true, info: %{is_admin: true}}) + moderator = insert(:user, local: true, is_moderator: true) + admin = insert(:user, local: true, is_admin: true) conn = conn @@ -61,6 +64,23 @@ defmodule Pleroma.Web.NodeInfoTest do assert Pleroma.Application.repository() == result["software"]["repository"] end + test "returns fieldsLimits field", %{conn: conn} do + Pleroma.Config.put([:instance, :max_account_fields], 10) + Pleroma.Config.put([:instance, :max_remote_account_fields], 15) + Pleroma.Config.put([:instance, :account_field_name_length], 255) + Pleroma.Config.put([:instance, :account_field_value_length], 2048) + + response = + conn + |> get("/nodeinfo/2.1.json") + |> json_response(:ok) + + assert response["metadata"]["fieldsLimits"]["maxFields"] == 10 + assert response["metadata"]["fieldsLimits"]["maxRemoteFields"] == 15 + assert response["metadata"]["fieldsLimits"]["nameLength"] == 255 + assert response["metadata"]["fieldsLimits"]["valueLength"] == 2048 + end + test "it returns the safe_dm_mentions feature if enabled", %{conn: conn} do option = Pleroma.Config.get([:instance, :safe_dm_mentions]) Pleroma.Config.put([:instance, :safe_dm_mentions], true) @@ -84,28 +104,28 @@ defmodule Pleroma.Web.NodeInfoTest do Pleroma.Config.put([:instance, :safe_dm_mentions], option) end - test "it shows if federation is enabled/disabled", %{conn: conn} do - original = Pleroma.Config.get([:instance, :federating]) + describe "`metadata/federation/enabled`" do + clear_config([:instance, :federating]) - Pleroma.Config.put([:instance, :federating], true) + test "it shows if federation is enabled/disabled", %{conn: conn} do + Pleroma.Config.put([:instance, :federating], true) - response = - conn - |> get("/nodeinfo/2.1.json") - |> json_response(:ok) - - assert response["metadata"]["federation"]["enabled"] == true + response = + conn + |> get("/nodeinfo/2.1.json") + |> json_response(:ok) - Pleroma.Config.put([:instance, :federating], false) + assert response["metadata"]["federation"]["enabled"] == true - response = - conn - |> get("/nodeinfo/2.1.json") - |> json_response(:ok) + Pleroma.Config.put([:instance, :federating], false) - assert response["metadata"]["federation"]["enabled"] == false + response = + conn + |> get("/nodeinfo/2.1.json") + |> json_response(:ok) - Pleroma.Config.put([:instance, :federating], original) + assert response["metadata"]["federation"]["enabled"] == false + end end test "it shows MRF transparency data if enabled", %{conn: conn} do diff --git a/test/web/oauth/app_test.exs b/test/web/oauth/app_test.exs new file mode 100644 index 000000000..899af648e --- /dev/null +++ b/test/web/oauth/app_test.exs @@ -0,0 +1,33 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.AppTest do + use Pleroma.DataCase + + alias Pleroma.Web.OAuth.App + import Pleroma.Factory + + describe "get_or_make/2" do + test "gets exist app" do + attrs = %{client_name: "Mastodon-Local", redirect_uris: "."} + app = insert(:oauth_app, Map.merge(attrs, %{scopes: ["read", "write"]})) + {:ok, %App{} = exist_app} = App.get_or_make(attrs, []) + assert exist_app == app + end + + test "make app" do + attrs = %{client_name: "Mastodon-Local", redirect_uris: "."} + {:ok, %App{} = app} = App.get_or_make(attrs, ["write"]) + assert app.scopes == ["write"] + end + + test "gets exist app and updates scopes" do + attrs = %{client_name: "Mastodon-Local", redirect_uris: "."} + app = insert(:oauth_app, Map.merge(attrs, %{scopes: ["read", "write"]})) + {:ok, %App{} = exist_app} = App.get_or_make(attrs, ["read", "write", "follow", "push"]) + assert exist_app.id == app.id + assert exist_app.scopes == ["read", "write", "follow", "push"] + end + end +end diff --git a/test/web/oauth/authorization_test.exs b/test/web/oauth/authorization_test.exs index d8b008437..d74b26cf8 100644 --- a/test/web/oauth/authorization_test.exs +++ b/test/web/oauth/authorization_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.AuthorizationTest do diff --git a/test/web/oauth/ldap_authorization_test.exs b/test/web/oauth/ldap_authorization_test.exs index 1cbe133b7..c55b0ffc5 100644 --- a/test/web/oauth/ldap_authorization_test.exs +++ b/test/web/oauth/ldap_authorization_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs index f24647a37..cff469c28 100644 --- a/test/web/oauth/oauth_controller_test.exs +++ b/test/web/oauth/oauth_controller_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.OAuthControllerTest do @@ -7,6 +7,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do import Pleroma.Factory alias Pleroma.Repo + alias Pleroma.User alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.OAuthController alias Pleroma.Web.OAuth.Token @@ -16,7 +17,8 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do key: "_test", signing_salt: "cooldude" ] - clear_config_all([:instance, :account_activation_required]) + + clear_config([:instance, :account_activation_required]) describe "in OAuth consumer mode, " do setup do @@ -449,7 +451,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do test "renders authentication page if user is already authenticated but `force_login` is tru-ish", %{app: app, conn: conn} do - token = insert(:oauth_token, app_id: app.id) + token = insert(:oauth_token, app: app) conn = conn @@ -473,7 +475,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do app: app, conn: conn } do - token = insert(:oauth_token, app_id: app.id) + token = insert(:oauth_token, app: app) conn = conn @@ -496,7 +498,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do app: app, conn: conn } do - token = insert(:oauth_token, app_id: app.id) + token = insert(:oauth_token, app: app) conn = conn @@ -522,7 +524,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do conn: conn } do unlisted_redirect_uri = "http://cross-site-request.com" - token = insert(:oauth_token, app_id: app.id) + token = insert(:oauth_token, app: app) conn = conn @@ -546,7 +548,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do app: app, conn: conn } do - token = insert(:oauth_token, app_id: app.id) + token = insert(:oauth_token, app: app) conn = conn @@ -566,33 +568,46 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do end describe "POST /oauth/authorize" do - test "redirects with oauth authorization" do - user = insert(:user) - app = insert(:oauth_app, scopes: ["read", "write", "follow"]) + test "redirects with oauth authorization, " <> + "granting requested app-supported scopes to both admin- and non-admin users" do + app_scopes = ["read", "write", "admin", "secret_scope"] + app = insert(:oauth_app, scopes: app_scopes) redirect_uri = OAuthController.default_redirect_uri(app) - conn = - build_conn() - |> post("/oauth/authorize", %{ - "authorization" => %{ - "name" => user.nickname, - "password" => "test", - "client_id" => app.client_id, - "redirect_uri" => redirect_uri, - "scope" => "read write", - "state" => "statepassed" - } - }) + non_admin = insert(:user, is_admin: false) + admin = insert(:user, is_admin: true) + scopes_subset = ["read:subscope", "write", "admin"] + + # In case scope param is missing, expecting _all_ app-supported scopes to be granted + for user <- [non_admin, admin], + {requested_scopes, expected_scopes} <- + %{scopes_subset => scopes_subset, nil => app_scopes} do + conn = + post( + build_conn(), + "/oauth/authorize", + %{ + "authorization" => %{ + "name" => user.nickname, + "password" => "test", + "client_id" => app.client_id, + "redirect_uri" => redirect_uri, + "scope" => requested_scopes, + "state" => "statepassed" + } + } + ) - target = redirected_to(conn) - assert target =~ redirect_uri + target = redirected_to(conn) + assert target =~ redirect_uri - query = URI.parse(target).query |> URI.query_decoder() |> Map.new() + query = URI.parse(target).query |> URI.query_decoder() |> Map.new() - assert %{"state" => "statepassed", "code" => code} = query - auth = Repo.get_by(Authorization, token: code) - assert auth - assert auth.scopes == ["read", "write"] + assert %{"state" => "statepassed", "code" => code} = query + auth = Repo.get_by(Authorization, token: code) + assert auth + assert auth.scopes == expected_scopes + end end test "returns 401 for wrong credentials", %{conn: conn} do @@ -622,13 +637,13 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do assert result =~ "Invalid Username/Password" end - test "returns 401 for missing scopes", %{conn: conn} do - user = insert(:user) - app = insert(:oauth_app) + test "returns 401 for missing scopes" do + user = insert(:user, is_admin: false) + app = insert(:oauth_app, scopes: ["read", "write", "admin"]) redirect_uri = OAuthController.default_redirect_uri(app) result = - conn + build_conn() |> post("/oauth/authorize", %{ "authorization" => %{ "name" => user.nickname, @@ -649,7 +664,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do assert result =~ "This action is outside the authorized scopes" end - test "returns 401 for scopes beyond app scopes", %{conn: conn} do + test "returns 401 for scopes beyond app scopes hierarchy", %{conn: conn} do user = insert(:user) app = insert(:oauth_app, scopes: ["read", "write"]) redirect_uri = OAuthController.default_redirect_uri(app) @@ -798,18 +813,14 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do test "rejects token exchange for valid credentials belonging to unconfirmed user and confirmation is required" do Pleroma.Config.put([:instance, :account_activation_required], true) - password = "testpassword" - user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password)) - info_change = Pleroma.User.Info.confirmation_changeset(user.info, need_confirmation: true) {:ok, user} = - user - |> Ecto.Changeset.change() - |> Ecto.Changeset.put_embed(:info, info_change) - |> Repo.update() + insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password)) + |> User.confirmation_changeset(need_confirmation: true) + |> User.update_and_set_cache() - refute Pleroma.User.auth_active?(user) + refute Pleroma.User.account_status(user) == :active app = insert(:oauth_app) @@ -834,12 +845,12 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password), - info: %{deactivated: true} + deactivated: true ) app = insert(:oauth_app) - conn = + resp = build_conn() |> post("/oauth/token", %{ "grant_type" => "password", @@ -848,10 +859,69 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do "client_id" => app.client_id, "client_secret" => app.client_secret }) + |> json_response(403) - assert resp = json_response(conn, 403) - assert %{"error" => _} = resp - refute Map.has_key?(resp, "access_token") + assert resp == %{ + "error" => "Your account is currently disabled", + "identifier" => "account_is_disabled" + } + end + + test "rejects token exchange for user with password_reset_pending set to true" do + password = "testpassword" + + user = + insert(:user, + password_hash: Comeonin.Pbkdf2.hashpwsalt(password), + password_reset_pending: true + ) + + app = insert(:oauth_app, scopes: ["read", "write"]) + + resp = + build_conn() + |> post("/oauth/token", %{ + "grant_type" => "password", + "username" => user.nickname, + "password" => password, + "client_id" => app.client_id, + "client_secret" => app.client_secret + }) + |> json_response(403) + + assert resp == %{ + "error" => "Password reset is required", + "identifier" => "password_reset_required" + } + end + + test "rejects token exchange for user with confirmation_pending set to true" do + Pleroma.Config.put([:instance, :account_activation_required], true) + password = "testpassword" + + user = + insert(:user, + password_hash: Comeonin.Pbkdf2.hashpwsalt(password), + confirmation_pending: true + ) + + app = insert(:oauth_app, scopes: ["read", "write"]) + + resp = + build_conn() + |> post("/oauth/token", %{ + "grant_type" => "password", + "username" => user.nickname, + "password" => password, + "client_id" => app.client_id, + "client_secret" => app.client_secret + }) + |> json_response(403) + + assert resp == %{ + "error" => "Your login is missing a confirmed e-mail address", + "identifier" => "missing_confirmed_email" + } end test "rejects an invalid authorization code" do diff --git a/test/web/oauth/token/utils_test.exs b/test/web/oauth/token/utils_test.exs index 20e338cab..a610d92f8 100644 --- a/test/web/oauth/token/utils_test.exs +++ b/test/web/oauth/token/utils_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.Token.UtilsTest do diff --git a/test/web/oauth/token_test.exs b/test/web/oauth/token_test.exs index 3c07309b7..40d71eb59 100644 --- a/test/web/oauth/token_test.exs +++ b/test/web/oauth/token_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.TokenTest do diff --git a/test/web/ostatus/activity_representer_test.exs b/test/web/ostatus/activity_representer_test.exs deleted file mode 100644 index a3a92ce5b..000000000 --- a/test/web/ostatus/activity_representer_test.exs +++ /dev/null @@ -1,300 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OStatus.ActivityRepresenterTest do - use Pleroma.DataCase - - alias Pleroma.Activity - alias Pleroma.Object - alias Pleroma.User - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.OStatus - alias Pleroma.Web.OStatus.ActivityRepresenter - - import Pleroma.Factory - import Tesla.Mock - - setup do - mock(fn env -> apply(HttpRequestMock, :request, [env]) end) - :ok - end - - test "an external note activity" do - incoming = File.read!("test/fixtures/mastodon-note-cw.xml") - {:ok, [activity]} = OStatus.handle_incoming(incoming) - - user = User.get_cached_by_ap_id(activity.data["actor"]) - - tuple = ActivityRepresenter.to_simple_form(activity, user) - - res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary() - - assert String.contains?( - res, - ~s{<link type="text/html" href="https://mastodon.social/users/lambadalambda/updates/2314748" rel="alternate"/>} - ) - end - - test "a note activity" do - note_activity = insert(:note_activity) - object_data = Object.normalize(note_activity).data - - user = User.get_cached_by_ap_id(note_activity.data["actor"]) - - expected = """ - <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> - <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> - <id>#{object_data["id"]}</id> - <title>New note by #{user.nickname}</title> - <content type="html">#{object_data["content"]}</content> - <published>#{object_data["published"]}</published> - <updated>#{object_data["published"]}</updated> - <ostatus:conversation ref="#{note_activity.data["context"]}">#{note_activity.data["context"]}</ostatus:conversation> - <link ref="#{note_activity.data["context"]}" rel="ostatus:conversation" /> - <summary>#{object_data["summary"]}</summary> - <link type="application/atom+xml" href="#{object_data["id"]}" rel="self" /> - <link type="text/html" href="#{object_data["id"]}" rel="alternate" /> - <category term="2hu"/> - <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> - <link name="2hu" rel="emoji" href="corndog.png" /> - """ - - tuple = ActivityRepresenter.to_simple_form(note_activity, user) - - res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary() - - assert clean(res) == clean(expected) - end - - test "a reply note" do - user = insert(:user) - note_object = insert(:note) - _note = insert(:note_activity, %{note: note_object}) - object = insert(:note, %{data: %{"inReplyTo" => note_object.data["id"]}}) - answer = insert(:note_activity, %{note: object}) - - Repo.update!( - Object.change(note_object, %{data: Map.put(note_object.data, "external_url", "someurl")}) - ) - - expected = """ - <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> - <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> - <id>#{object.data["id"]}</id> - <title>New note by #{user.nickname}</title> - <content type="html">#{object.data["content"]}</content> - <published>#{object.data["published"]}</published> - <updated>#{object.data["published"]}</updated> - <ostatus:conversation ref="#{answer.data["context"]}">#{answer.data["context"]}</ostatus:conversation> - <link ref="#{answer.data["context"]}" rel="ostatus:conversation" /> - <summary>2hu</summary> - <link type="application/atom+xml" href="#{object.data["id"]}" rel="self" /> - <link type="text/html" href="#{object.data["id"]}" rel="alternate" /> - <category term="2hu"/> - <thr:in-reply-to ref="#{note_object.data["id"]}" href="someurl" /> - <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> - <link name="2hu" rel="emoji" href="corndog.png" /> - """ - - tuple = ActivityRepresenter.to_simple_form(answer, user) - - res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary() - - assert clean(res) == clean(expected) - end - - test "an announce activity" do - note = insert(:note_activity) - user = insert(:user) - object = Object.normalize(note) - - {:ok, announce, _object} = ActivityPub.announce(user, object) - - announce = Activity.get_by_id(announce.id) - - note_user = User.get_cached_by_ap_id(note.data["actor"]) - note = Activity.get_by_id(note.id) - - note_xml = - ActivityRepresenter.to_simple_form(note, note_user, true) - |> :xmerl.export_simple_content(:xmerl_xml) - |> to_string - - expected = """ - <activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type> - <activity:verb>http://activitystrea.ms/schema/1.0/share</activity:verb> - <id>#{announce.data["id"]}</id> - <title>#{user.nickname} repeated a notice</title> - <content type="html">RT #{object.data["content"]}</content> - <published>#{announce.data["published"]}</published> - <updated>#{announce.data["published"]}</updated> - <ostatus:conversation ref="#{announce.data["context"]}">#{announce.data["context"]}</ostatus:conversation> - <link ref="#{announce.data["context"]}" rel="ostatus:conversation" /> - <link rel="self" type="application/atom+xml" href="#{announce.data["id"]}"/> - <activity:object> - #{note_xml} - </activity:object> - <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="#{ - note.data["actor"] - }"/> - <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> - """ - - announce_xml = - ActivityRepresenter.to_simple_form(announce, user) - |> :xmerl.export_simple_content(:xmerl_xml) - |> to_string - - assert clean(expected) == clean(announce_xml) - end - - test "a like activity" do - note = insert(:note) - user = insert(:user) - {:ok, like, _note} = ActivityPub.like(user, note) - - tuple = ActivityRepresenter.to_simple_form(like, user) - refute is_nil(tuple) - - res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary() - - expected = """ - <activity:verb>http://activitystrea.ms/schema/1.0/favorite</activity:verb> - <id>#{like.data["id"]}</id> - <title>New favorite by #{user.nickname}</title> - <content type="html">#{user.nickname} favorited something</content> - <published>#{like.data["published"]}</published> - <updated>#{like.data["published"]}</updated> - <activity:object> - <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> - <id>#{note.data["id"]}</id> - </activity:object> - <ostatus:conversation ref="#{like.data["context"]}">#{like.data["context"]}</ostatus:conversation> - <link ref="#{like.data["context"]}" rel="ostatus:conversation" /> - <link rel="self" type="application/atom+xml" href="#{like.data["id"]}"/> - <thr:in-reply-to ref="#{note.data["id"]}" /> - <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="#{ - note.data["actor"] - }"/> - <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> - """ - - assert clean(res) == clean(expected) - end - - test "a follow activity" do - follower = insert(:user) - followed = insert(:user) - - {:ok, activity} = - ActivityPub.insert(%{ - "type" => "Follow", - "actor" => follower.ap_id, - "object" => followed.ap_id, - "to" => [followed.ap_id] - }) - - tuple = ActivityRepresenter.to_simple_form(activity, follower) - - refute is_nil(tuple) - - res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary() - - expected = """ - <activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type> - <activity:verb>http://activitystrea.ms/schema/1.0/follow</activity:verb> - <id>#{activity.data["id"]}</id> - <title>#{follower.nickname} started following #{activity.data["object"]}</title> - <content type="html"> #{follower.nickname} started following #{activity.data["object"]}</content> - <published>#{activity.data["published"]}</published> - <updated>#{activity.data["published"]}</updated> - <activity:object> - <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type> - <id>#{activity.data["object"]}</id> - <uri>#{activity.data["object"]}</uri> - </activity:object> - <link rel="self" type="application/atom+xml" href="#{activity.data["id"]}"/> - <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="#{ - activity.data["object"] - }"/> - """ - - assert clean(res) == clean(expected) - end - - test "an unfollow activity" do - follower = insert(:user) - followed = insert(:user) - {:ok, _activity} = ActivityPub.follow(follower, followed) - {:ok, activity} = ActivityPub.unfollow(follower, followed) - - tuple = ActivityRepresenter.to_simple_form(activity, follower) - - refute is_nil(tuple) - - res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary() - - expected = """ - <activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type> - <activity:verb>http://activitystrea.ms/schema/1.0/unfollow</activity:verb> - <id>#{activity.data["id"]}</id> - <title>#{follower.nickname} stopped following #{followed.ap_id}</title> - <content type="html"> #{follower.nickname} stopped following #{followed.ap_id}</content> - <published>#{activity.data["published"]}</published> - <updated>#{activity.data["published"]}</updated> - <activity:object> - <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type> - <id>#{followed.ap_id}</id> - <uri>#{followed.ap_id}</uri> - </activity:object> - <link rel="self" type="application/atom+xml" href="#{activity.data["id"]}"/> - <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="#{ - followed.ap_id - }"/> - """ - - assert clean(res) == clean(expected) - end - - test "a delete" do - user = insert(:user) - - activity = %Activity{ - data: %{ - "id" => "ap_id", - "type" => "Delete", - "actor" => user.ap_id, - "object" => "some_id", - "published" => "2017-06-18T12:00:18+00:00" - } - } - - tuple = ActivityRepresenter.to_simple_form(activity, nil) - - refute is_nil(tuple) - - res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary() - - expected = """ - <activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type> - <activity:verb>http://activitystrea.ms/schema/1.0/delete</activity:verb> - <id>#{activity.data["object"]}</id> - <title>An object was deleted</title> - <content type="html">An object was deleted</content> - <published>#{activity.data["published"]}</published> - <updated>#{activity.data["published"]}</updated> - """ - - assert clean(res) == clean(expected) - end - - test "an unknown activity" do - tuple = ActivityRepresenter.to_simple_form(%Activity{}, nil) - assert is_nil(tuple) - end - - defp clean(string) do - String.replace(string, ~r/\s/, "") - end -end diff --git a/test/web/ostatus/feed_representer_test.exs b/test/web/ostatus/feed_representer_test.exs deleted file mode 100644 index 3c7b126e7..000000000 --- a/test/web/ostatus/feed_representer_test.exs +++ /dev/null @@ -1,59 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OStatus.FeedRepresenterTest do - use Pleroma.DataCase - import Pleroma.Factory - alias Pleroma.User - alias Pleroma.Web.OStatus - alias Pleroma.Web.OStatus.ActivityRepresenter - alias Pleroma.Web.OStatus.FeedRepresenter - alias Pleroma.Web.OStatus.UserRepresenter - - test "returns a feed of the last 20 items of the user" do - note_activity = insert(:note_activity) - user = User.get_cached_by_ap_id(note_activity.data["actor"]) - - tuple = FeedRepresenter.to_simple_form(user, [note_activity], [user]) - - most_recent_update = - note_activity.updated_at - |> NaiveDateTime.to_iso8601() - - res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> to_string - - user_xml = - UserRepresenter.to_simple_form(user) - |> :xmerl.export_simple_content(:xmerl_xml) - - entry_xml = - ActivityRepresenter.to_simple_form(note_activity, user) - |> :xmerl.export_simple_content(:xmerl_xml) - - expected = """ - <feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:ostatus="http://ostatus.org/schema/1.0"> - <id>#{OStatus.feed_path(user)}</id> - <title>#{user.nickname}'s timeline</title> - <updated>#{most_recent_update}</updated> - <logo>#{User.avatar_url(user)}</logo> - <link rel="hub" href="#{OStatus.pubsub_path(user)}" /> - <link rel="salmon" href="#{OStatus.salmon_path(user)}" /> - <link rel="self" href="#{OStatus.feed_path(user)}" type="application/atom+xml" /> - <author> - #{user_xml} - </author> - <link rel="next" href="#{OStatus.feed_path(user)}?max_id=#{note_activity.id}" type="application/atom+xml" /> - <entry> - #{entry_xml} - </entry> - </feed> - """ - - assert clean(res) == clean(expected) - end - - defp clean(string) do - String.replace(string, ~r/\s/, "") - end -end diff --git a/test/web/ostatus/incoming_documents/delete_handling_test.exs b/test/web/ostatus/incoming_documents/delete_handling_test.exs deleted file mode 100644 index cd0447af7..000000000 --- a/test/web/ostatus/incoming_documents/delete_handling_test.exs +++ /dev/null @@ -1,48 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OStatus.DeleteHandlingTest do - use Pleroma.DataCase - - import Pleroma.Factory - import Tesla.Mock - - alias Pleroma.Activity - alias Pleroma.Object - alias Pleroma.Web.OStatus - - setup do - mock(fn env -> apply(HttpRequestMock, :request, [env]) end) - :ok - end - - describe "deletions" do - test "it removes the mentioned activity" do - note = insert(:note_activity) - second_note = insert(:note_activity) - object = Object.normalize(note) - second_object = Object.normalize(second_note) - user = insert(:user) - - {:ok, like, _object} = Pleroma.Web.ActivityPub.ActivityPub.like(user, object) - - incoming = - File.read!("test/fixtures/delete.xml") - |> String.replace( - "tag:mastodon.sdf.org,2017-06-10:objectId=310513:objectType=Status", - object.data["id"] - ) - - {:ok, [delete]} = OStatus.handle_incoming(incoming) - - refute Activity.get_by_id(note.id) - refute Activity.get_by_id(like.id) - assert Object.get_by_ap_id(object.data["id"]).data["type"] == "Tombstone" - assert Activity.get_by_id(second_note.id) - assert Object.get_by_ap_id(second_object.data["id"]) - - assert delete.data["type"] == "Delete" - end - end -end diff --git a/test/web/ostatus/ostatus_controller_test.exs b/test/web/ostatus/ostatus_controller_test.exs index 095ae7041..2051841c2 100644 --- a/test/web/ostatus/ostatus_controller_test.exs +++ b/test/web/ostatus/ostatus_controller_test.exs @@ -1,17 +1,15 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OStatus.OStatusControllerTest do use Pleroma.Web.ConnCase - import ExUnit.CaptureLog import Pleroma.Factory alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.CommonAPI - alias Pleroma.Web.OStatus.ActivityRepresenter setup_all do Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -22,104 +20,7 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do Pleroma.Config.put([:instance, :federating], true) end - describe "salmon_incoming" do - test "decodes a salmon", %{conn: conn} do - user = insert(:user) - salmon = File.read!("test/fixtures/salmon.xml") - - assert capture_log(fn -> - conn = - conn - |> put_req_header("content-type", "application/atom+xml") - |> post("/users/#{user.nickname}/salmon", salmon) - - assert response(conn, 200) - end) =~ "[error]" - end - - test "decodes a salmon with a changed magic key", %{conn: conn} do - user = insert(:user) - salmon = File.read!("test/fixtures/salmon.xml") - - assert capture_log(fn -> - conn = - conn - |> put_req_header("content-type", "application/atom+xml") - |> post("/users/#{user.nickname}/salmon", salmon) - - assert response(conn, 200) - end) =~ "[error]" - - # Set a wrong magic-key for a user so it has to refetch - salmon_user = User.get_cached_by_ap_id("http://gs.example.org:4040/index.php/user/1") - - # Wrong key - info_cng = - User.Info.remote_user_creation(salmon_user.info, %{ - magic_key: - "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwrong1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB" - }) - - salmon_user - |> Ecto.Changeset.change() - |> Ecto.Changeset.put_embed(:info, info_cng) - |> User.update_and_set_cache() - - assert capture_log(fn -> - conn = - build_conn() - |> put_req_header("content-type", "application/atom+xml") - |> post("/users/#{user.nickname}/salmon", salmon) - - assert response(conn, 200) - end) =~ "[error]" - end - end - - test "gets a feed", %{conn: conn} do - note_activity = insert(:note_activity) - object = Object.normalize(note_activity) - user = User.get_cached_by_ap_id(note_activity.data["actor"]) - - conn = - conn - |> put_req_header("content-type", "application/atom+xml") - |> get("/users/#{user.nickname}/feed.atom") - - assert response(conn, 200) =~ object.data["content"] - end - - test "returns 404 for a missing feed", %{conn: conn} do - conn = - conn - |> put_req_header("content-type", "application/atom+xml") - |> get("/users/nonexisting/feed.atom") - - assert response(conn, 404) - end - describe "GET object/2" do - test "gets an object", %{conn: conn} do - note_activity = insert(:note_activity) - object = Object.normalize(note_activity) - user = User.get_cached_by_ap_id(note_activity.data["actor"]) - [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) - url = "/objects/#{uuid}" - - conn = - conn - |> put_req_header("accept", "application/xml") - |> get(url) - - expected = - ActivityRepresenter.to_simple_form(note_activity, user, true) - |> ActivityRepresenter.wrap_with_entry() - |> :xmerl.export_simple(:xmerl_xml) - |> to_string - - assert response(conn, 200) == expected - end - test "redirects to /notice/id for html format", %{conn: conn} do note_activity = insert(:note_activity) object = Object.normalize(note_activity) @@ -134,23 +35,6 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do assert redirected_to(conn) == "/notice/#{note_activity.id}" end - test "500s when user not found", %{conn: conn} do - note_activity = insert(:note_activity) - object = Object.normalize(note_activity) - user = User.get_cached_by_ap_id(note_activity.data["actor"]) - User.invalidate_cache(user) - Pleroma.Repo.delete(user) - [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) - url = "/objects/#{uuid}" - - conn = - conn - |> put_req_header("accept", "application/xml") - |> get(url) - - assert response(conn, 500) == ~S({"error":"Something went wrong"}) - end - test "404s on private objects", %{conn: conn} do note_activity = insert(:direct_note_activity) object = Object.normalize(note_activity) @@ -169,16 +53,6 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do end describe "GET activity/2" do - test "gets an activity in xml format", %{conn: conn} do - note_activity = insert(:note_activity) - [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) - - conn - |> put_req_header("accept", "application/xml") - |> get("/activities/#{uuid}") - |> response(200) - end - test "redirects to /notice/id for html format", %{conn: conn} do note_activity = insert(:note_activity) [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) @@ -191,39 +65,6 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do assert redirected_to(conn) == "/notice/#{note_activity.id}" end - test "505s when user not found", %{conn: conn} do - note_activity = insert(:note_activity) - [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) - user = User.get_cached_by_ap_id(note_activity.data["actor"]) - User.invalidate_cache(user) - Pleroma.Repo.delete(user) - - conn = - conn - |> put_req_header("accept", "text/html") - |> get("/activities/#{uuid}") - - assert response(conn, 500) == ~S({"error":"Something went wrong"}) - end - - test "404s on deleted objects", %{conn: conn} do - note_activity = insert(:note_activity) - object = Object.normalize(note_activity) - [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) - - conn - |> put_req_header("accept", "application/xml") - |> get("/objects/#{uuid}") - |> response(200) - - Object.delete(object) - - conn - |> put_req_header("accept", "application/xml") - |> get("/objects/#{uuid}") - |> response(404) - end - test "404s on private activities", %{conn: conn} do note_activity = insert(:direct_note_activity) [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) @@ -254,21 +95,28 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do end describe "GET notice/2" do - test "gets a notice in xml format", %{conn: conn} do + test "redirects to a proper object URL when json requested and the object is local", %{ + conn: conn + } do note_activity = insert(:note_activity) + expected_redirect_url = Object.normalize(note_activity).data["id"] - conn - |> get("/notice/#{note_activity.id}") - |> response(200) + redirect_url = + conn + |> put_req_header("accept", "application/activity+json") + |> get("/notice/#{note_activity.id}") + |> redirected_to() + + assert redirect_url == expected_redirect_url end - test "gets a notice in AS2 format", %{conn: conn} do - note_activity = insert(:note_activity) + test "returns a 404 on remote notice when json requested", %{conn: conn} do + note_activity = insert(:note_activity, local: false) conn |> put_req_header("accept", "application/activity+json") |> get("/notice/#{note_activity.id}") - |> json_response(200) + |> response(404) end test "500s when actor not found", %{conn: conn} do @@ -284,32 +132,6 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do assert response(conn, 500) == ~S({"error":"Something went wrong"}) end - test "only gets a notice in AS2 format for Create messages", %{conn: conn} do - note_activity = insert(:note_activity) - url = "/notice/#{note_activity.id}" - - conn = - conn - |> put_req_header("accept", "application/activity+json") - |> get(url) - - assert json_response(conn, 200) - - user = insert(:user) - - {:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user) - url = "/notice/#{like_activity.id}" - - assert like_activity.data["type"] == "Like" - - conn = - build_conn() - |> put_req_header("accept", "application/activity+json") - |> get(url) - - assert response(conn, 404) - end - test "render html for redirect for html format", %{conn: conn} do note_activity = insert(:note_activity) @@ -359,183 +181,6 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do end end - describe "feed_redirect" do - test "undefined format. it redirects to feed", %{conn: conn} do - note_activity = insert(:note_activity) - user = User.get_cached_by_ap_id(note_activity.data["actor"]) - - response = - conn - |> put_req_header("accept", "application/xml") - |> get("/users/#{user.nickname}") - |> response(302) - - assert response == - "<html><body>You are being <a href=\"#{Pleroma.Web.base_url()}/users/#{ - user.nickname - }/feed.atom\">redirected</a>.</body></html>" - end - - test "undefined format. it returns error when user not found", %{conn: conn} do - response = - conn - |> put_req_header("accept", "application/xml") - |> get("/users/jimm") - |> response(404) - - assert response == ~S({"error":"Not found"}) - end - - test "activity+json format. it redirects on actual feed of user", %{conn: conn} do - note_activity = insert(:note_activity) - user = User.get_cached_by_ap_id(note_activity.data["actor"]) - - response = - conn - |> put_req_header("accept", "application/activity+json") - |> get("/users/#{user.nickname}") - |> json_response(200) - - assert response["endpoints"] == %{ - "oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize", - "oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps", - "oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token", - "sharedInbox" => "#{Pleroma.Web.base_url()}/inbox" - } - - assert response["@context"] == [ - "https://www.w3.org/ns/activitystreams", - "http://localhost:4001/schemas/litepub-0.1.jsonld", - %{"@language" => "und"} - ] - - assert Map.take(response, [ - "followers", - "following", - "id", - "inbox", - "manuallyApprovesFollowers", - "name", - "outbox", - "preferredUsername", - "summary", - "tag", - "type", - "url" - ]) == %{ - "followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers", - "following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following", - "id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}", - "inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox", - "manuallyApprovesFollowers" => false, - "name" => user.name, - "outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox", - "preferredUsername" => user.nickname, - "summary" => user.bio, - "tag" => [], - "type" => "Person", - "url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}" - } - end - - test "activity+json format. it returns error whe use not found", %{conn: conn} do - response = - conn - |> put_req_header("accept", "application/activity+json") - |> get("/users/jimm") - |> json_response(404) - - assert response == "Not found" - end - - test "json format. it redirects on actual feed of user", %{conn: conn} do - note_activity = insert(:note_activity) - user = User.get_cached_by_ap_id(note_activity.data["actor"]) - - response = - conn - |> put_req_header("accept", "application/json") - |> get("/users/#{user.nickname}") - |> json_response(200) - - assert response["endpoints"] == %{ - "oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize", - "oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps", - "oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token", - "sharedInbox" => "#{Pleroma.Web.base_url()}/inbox" - } - - assert response["@context"] == [ - "https://www.w3.org/ns/activitystreams", - "http://localhost:4001/schemas/litepub-0.1.jsonld", - %{"@language" => "und"} - ] - - assert Map.take(response, [ - "followers", - "following", - "id", - "inbox", - "manuallyApprovesFollowers", - "name", - "outbox", - "preferredUsername", - "summary", - "tag", - "type", - "url" - ]) == %{ - "followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers", - "following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following", - "id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}", - "inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox", - "manuallyApprovesFollowers" => false, - "name" => user.name, - "outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox", - "preferredUsername" => user.nickname, - "summary" => user.bio, - "tag" => [], - "type" => "Person", - "url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}" - } - end - - test "json format. it returns error whe use not found", %{conn: conn} do - response = - conn - |> put_req_header("accept", "application/json") - |> get("/users/jimm") - |> json_response(404) - - assert response == "Not found" - end - - test "html format. it redirects on actual feed of user", %{conn: conn} do - note_activity = insert(:note_activity) - user = User.get_cached_by_ap_id(note_activity.data["actor"]) - - response = - conn - |> get("/users/#{user.nickname}") - |> response(200) - - assert response == - Fallback.RedirectController.redirector_with_meta( - conn, - %{user: user} - ).resp_body - end - - test "html format. it returns error when user not found", %{conn: conn} do - response = - conn - |> get("/users/jimm") - |> json_response(404) - - assert response == %{"error" => "Not found"} - end - end - describe "GET /notice/:id/embed_player" do test "render embed player", %{conn: conn} do note_activity = insert(:note_activity) diff --git a/test/web/ostatus/ostatus_test.exs b/test/web/ostatus/ostatus_test.exs deleted file mode 100644 index 803a97695..000000000 --- a/test/web/ostatus/ostatus_test.exs +++ /dev/null @@ -1,631 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OStatusTest do - use Pleroma.DataCase - alias Pleroma.Activity - alias Pleroma.Instances - alias Pleroma.Object - alias Pleroma.Repo - alias Pleroma.User - alias Pleroma.Web.OStatus - alias Pleroma.Web.XML - - import ExUnit.CaptureLog - import Mock - import Pleroma.Factory - - setup_all do - Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) - :ok - end - - test "don't insert create notes twice" do - incoming = File.read!("test/fixtures/incoming_note_activity.xml") - {:ok, [activity]} = OStatus.handle_incoming(incoming) - assert {:ok, [activity]} == OStatus.handle_incoming(incoming) - end - - test "handle incoming note - GS, Salmon" do - incoming = File.read!("test/fixtures/incoming_note_activity.xml") - {:ok, [activity]} = OStatus.handle_incoming(incoming) - object = Object.normalize(activity) - - user = User.get_cached_by_ap_id(activity.data["actor"]) - assert user.info.note_count == 1 - assert activity.data["type"] == "Create" - assert object.data["type"] == "Note" - - assert object.data["id"] == "tag:gs.example.org:4040,2017-04-23:noticeId=29:objectType=note" - - assert activity.data["published"] == "2017-04-23T14:51:03+00:00" - assert object.data["published"] == "2017-04-23T14:51:03+00:00" - - assert activity.data["context"] == - "tag:gs.example.org:4040,2017-04-23:objectType=thread:nonce=f09e22f58abd5c7b" - - assert "http://pleroma.example.org:4000/users/lain3" in activity.data["to"] - assert object.data["emoji"] == %{"marko" => "marko.png", "reimu" => "reimu.png"} - assert activity.local == false - end - - test "handle incoming notes - GS, subscription" do - incoming = File.read!("test/fixtures/ostatus_incoming_post.xml") - {:ok, [activity]} = OStatus.handle_incoming(incoming) - object = Object.normalize(activity) - - assert activity.data["type"] == "Create" - assert object.data["type"] == "Note" - assert object.data["actor"] == "https://social.heldscal.la/user/23211" - assert object.data["content"] == "Will it blend?" - user = User.get_cached_by_ap_id(activity.data["actor"]) - assert User.ap_followers(user) in activity.data["to"] - assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"] - end - - test "handle incoming notes with attachments - GS, subscription" do - incoming = File.read!("test/fixtures/incoming_websub_gnusocial_attachments.xml") - {:ok, [activity]} = OStatus.handle_incoming(incoming) - object = Object.normalize(activity) - - assert activity.data["type"] == "Create" - assert object.data["type"] == "Note" - assert object.data["actor"] == "https://social.heldscal.la/user/23211" - assert object.data["attachment"] |> length == 2 - assert object.data["external_url"] == "https://social.heldscal.la/notice/2020923" - assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"] - end - - test "handle incoming notes with tags" do - incoming = File.read!("test/fixtures/ostatus_incoming_post_tag.xml") - {:ok, [activity]} = OStatus.handle_incoming(incoming) - object = Object.normalize(activity) - - assert object.data["tag"] == ["nsfw"] - assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"] - end - - test "handle incoming notes - Mastodon, salmon, reply" do - # It uses the context of the replied to object - Repo.insert!(%Object{ - data: %{ - "id" => "https://pleroma.soykaf.com/objects/c237d966-ac75-4fe3-a87a-d89d71a3a7a4", - "context" => "2hu" - } - }) - - incoming = File.read!("test/fixtures/incoming_reply_mastodon.xml") - {:ok, [activity]} = OStatus.handle_incoming(incoming) - object = Object.normalize(activity) - - assert activity.data["type"] == "Create" - assert object.data["type"] == "Note" - assert object.data["actor"] == "https://mastodon.social/users/lambadalambda" - assert activity.data["context"] == "2hu" - assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"] - end - - test "handle incoming notes - Mastodon, with CW" do - incoming = File.read!("test/fixtures/mastodon-note-cw.xml") - {:ok, [activity]} = OStatus.handle_incoming(incoming) - object = Object.normalize(activity) - - assert activity.data["type"] == "Create" - assert object.data["type"] == "Note" - assert object.data["actor"] == "https://mastodon.social/users/lambadalambda" - assert object.data["summary"] == "technologic" - assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"] - end - - test "handle incoming unlisted messages, put public into cc" do - incoming = File.read!("test/fixtures/mastodon-note-unlisted.xml") - {:ok, [activity]} = OStatus.handle_incoming(incoming) - object = Object.normalize(activity) - - refute "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"] - assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["cc"] - refute "https://www.w3.org/ns/activitystreams#Public" in object.data["to"] - assert "https://www.w3.org/ns/activitystreams#Public" in object.data["cc"] - end - - test "handle incoming retweets - Mastodon, with CW" do - incoming = File.read!("test/fixtures/cw_retweet.xml") - {:ok, [[_activity, retweeted_activity]]} = OStatus.handle_incoming(incoming) - retweeted_object = Object.normalize(retweeted_activity) - - assert retweeted_object.data["summary"] == "Hey." - end - - test "handle incoming notes - GS, subscription, reply" do - incoming = File.read!("test/fixtures/ostatus_incoming_reply.xml") - {:ok, [activity]} = OStatus.handle_incoming(incoming) - object = Object.normalize(activity) - - assert activity.data["type"] == "Create" - assert object.data["type"] == "Note" - assert object.data["actor"] == "https://social.heldscal.la/user/23211" - - assert object.data["content"] == - "@<a href=\"https://gs.archae.me/user/4687\" class=\"h-card u-url p-nickname mention\" title=\"shpbot\">shpbot</a> why not indeed." - - assert object.data["inReplyTo"] == - "tag:gs.archae.me,2017-04-30:noticeId=778260:objectType=note" - - assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"] - end - - test "handle incoming retweets - GS, subscription" do - incoming = File.read!("test/fixtures/share-gs.xml") - {:ok, [[activity, retweeted_activity]]} = OStatus.handle_incoming(incoming) - - assert activity.data["type"] == "Announce" - assert activity.data["actor"] == "https://social.heldscal.la/user/23211" - assert activity.data["object"] == retweeted_activity.data["object"] - assert "https://pleroma.soykaf.com/users/lain" in activity.data["to"] - refute activity.local - - retweeted_activity = Activity.get_by_id(retweeted_activity.id) - retweeted_object = Object.normalize(retweeted_activity) - assert retweeted_activity.data["type"] == "Create" - assert retweeted_activity.data["actor"] == "https://pleroma.soykaf.com/users/lain" - refute retweeted_activity.local - assert retweeted_object.data["announcement_count"] == 1 - assert String.contains?(retweeted_object.data["content"], "mastodon") - refute String.contains?(retweeted_object.data["content"], "Test account") - end - - test "handle incoming retweets - GS, subscription - local message" do - incoming = File.read!("test/fixtures/share-gs-local.xml") - note_activity = insert(:note_activity) - object = Object.normalize(note_activity) - user = User.get_cached_by_ap_id(note_activity.data["actor"]) - - incoming = - incoming - |> String.replace("LOCAL_ID", object.data["id"]) - |> String.replace("LOCAL_USER", user.ap_id) - - {:ok, [[activity, retweeted_activity]]} = OStatus.handle_incoming(incoming) - - assert activity.data["type"] == "Announce" - assert activity.data["actor"] == "https://social.heldscal.la/user/23211" - assert activity.data["object"] == object.data["id"] - assert user.ap_id in activity.data["to"] - refute activity.local - - retweeted_activity = Activity.get_by_id(retweeted_activity.id) - assert note_activity.id == retweeted_activity.id - assert retweeted_activity.data["type"] == "Create" - assert retweeted_activity.data["actor"] == user.ap_id - assert retweeted_activity.local - assert Object.normalize(retweeted_activity).data["announcement_count"] == 1 - end - - test "handle incoming retweets - Mastodon, salmon" do - incoming = File.read!("test/fixtures/share.xml") - {:ok, [[activity, retweeted_activity]]} = OStatus.handle_incoming(incoming) - retweeted_object = Object.normalize(retweeted_activity) - - assert activity.data["type"] == "Announce" - assert activity.data["actor"] == "https://mastodon.social/users/lambadalambda" - assert activity.data["object"] == retweeted_activity.data["object"] - - assert activity.data["id"] == - "tag:mastodon.social,2017-05-03:objectId=4934452:objectType=Status" - - refute activity.local - assert retweeted_activity.data["type"] == "Create" - assert retweeted_activity.data["actor"] == "https://pleroma.soykaf.com/users/lain" - refute retweeted_activity.local - refute String.contains?(retweeted_object.data["content"], "Test account") - end - - test "handle incoming favorites - GS, websub" do - capture_log(fn -> - incoming = File.read!("test/fixtures/favorite.xml") - {:ok, [[activity, favorited_activity]]} = OStatus.handle_incoming(incoming) - - assert activity.data["type"] == "Like" - assert activity.data["actor"] == "https://social.heldscal.la/user/23211" - assert activity.data["object"] == favorited_activity.data["object"] - - assert activity.data["id"] == - "tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061643:2017-05-05T09:12:50+00:00" - - refute activity.local - assert favorited_activity.data["type"] == "Create" - assert favorited_activity.data["actor"] == "https://shitposter.club/user/1" - - assert favorited_activity.data["object"] == - "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment" - - refute favorited_activity.local - end) - end - - test "handle conversation references" do - incoming = File.read!("test/fixtures/mastodon_conversation.xml") - {:ok, [activity]} = OStatus.handle_incoming(incoming) - - assert activity.data["context"] == - "tag:mastodon.social,2017-08-28:objectId=7876885:objectType=Conversation" - end - - test "handle incoming favorites with locally available object - GS, websub" do - note_activity = insert(:note_activity) - object = Object.normalize(note_activity) - - incoming = - File.read!("test/fixtures/favorite_with_local_note.xml") - |> String.replace("localid", object.data["id"]) - - {:ok, [[activity, favorited_activity]]} = OStatus.handle_incoming(incoming) - - assert activity.data["type"] == "Like" - assert activity.data["actor"] == "https://social.heldscal.la/user/23211" - assert activity.data["object"] == object.data["id"] - refute activity.local - assert note_activity.id == favorited_activity.id - assert favorited_activity.local - end - - test_with_mock "handle incoming replies, fetching replied-to activities if we don't have them", - OStatus, - [:passthrough], - [] do - incoming = File.read!("test/fixtures/incoming_note_activity_answer.xml") - {:ok, [activity]} = OStatus.handle_incoming(incoming) - object = Object.normalize(activity, false) - - assert activity.data["type"] == "Create" - assert object.data["type"] == "Note" - - assert object.data["inReplyTo"] == - "http://pleroma.example.org:4000/objects/55bce8fc-b423-46b1-af71-3759ab4670bc" - - assert "http://pleroma.example.org:4000/users/lain5" in activity.data["to"] - - assert object.data["id"] == "tag:gs.example.org:4040,2017-04-25:noticeId=55:objectType=note" - - assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"] - - assert called(OStatus.fetch_activity_from_url(object.data["inReplyTo"], :_)) - end - - test_with_mock "handle incoming replies, not fetching replied-to activities beyond max_replies_depth", - OStatus, - [:passthrough], - [] do - incoming = File.read!("test/fixtures/incoming_note_activity_answer.xml") - - with_mock Pleroma.Web.Federator, - allowed_incoming_reply_depth?: fn _ -> false end do - {:ok, [activity]} = OStatus.handle_incoming(incoming) - object = Object.normalize(activity, false) - - refute called(OStatus.fetch_activity_from_url(object.data["inReplyTo"], :_)) - end - end - - test "handle incoming follows" do - incoming = File.read!("test/fixtures/follow.xml") - {:ok, [activity]} = OStatus.handle_incoming(incoming) - assert activity.data["type"] == "Follow" - - assert activity.data["id"] == - "tag:social.heldscal.la,2017-05-07:subscription:23211:person:44803:2017-05-07T09:54:48+00:00" - - assert activity.data["actor"] == "https://social.heldscal.la/user/23211" - assert activity.data["object"] == "https://pawoo.net/users/pekorino" - refute activity.local - - follower = User.get_cached_by_ap_id(activity.data["actor"]) - followed = User.get_cached_by_ap_id(activity.data["object"]) - - assert User.following?(follower, followed) - end - - test "refuse following over OStatus if the followed's account is locked" do - incoming = File.read!("test/fixtures/follow.xml") - _user = insert(:user, info: %{locked: true}, ap_id: "https://pawoo.net/users/pekorino") - - {:ok, [{:error, "It's not possible to follow locked accounts over OStatus"}]} = - OStatus.handle_incoming(incoming) - end - - test "handle incoming unfollows with existing follow" do - incoming_follow = File.read!("test/fixtures/follow.xml") - {:ok, [_activity]} = OStatus.handle_incoming(incoming_follow) - - incoming = File.read!("test/fixtures/unfollow.xml") - {:ok, [activity]} = OStatus.handle_incoming(incoming) - - assert activity.data["type"] == "Undo" - - assert activity.data["id"] == - "undo:tag:social.heldscal.la,2017-05-07:subscription:23211:person:44803:2017-05-07T09:54:48+00:00" - - assert activity.data["actor"] == "https://social.heldscal.la/user/23211" - embedded_object = activity.data["object"] - assert is_map(embedded_object) - assert embedded_object["type"] == "Follow" - assert embedded_object["object"] == "https://pawoo.net/users/pekorino" - refute activity.local - - follower = User.get_cached_by_ap_id(activity.data["actor"]) - followed = User.get_cached_by_ap_id(embedded_object["object"]) - - refute User.following?(follower, followed) - end - - test "it clears `unreachable` federation status of the sender" do - incoming_reaction_xml = File.read!("test/fixtures/share-gs.xml") - doc = XML.parse_document(incoming_reaction_xml) - actor_uri = XML.string_from_xpath("//author/uri[1]", doc) - reacted_to_author_uri = XML.string_from_xpath("//author/uri[2]", doc) - - Instances.set_consistently_unreachable(actor_uri) - Instances.set_consistently_unreachable(reacted_to_author_uri) - refute Instances.reachable?(actor_uri) - refute Instances.reachable?(reacted_to_author_uri) - - {:ok, _} = OStatus.handle_incoming(incoming_reaction_xml) - assert Instances.reachable?(actor_uri) - refute Instances.reachable?(reacted_to_author_uri) - end - - describe "new remote user creation" do - test "returns local users" do - local_user = insert(:user) - {:ok, user} = OStatus.find_or_make_user(local_user.ap_id) - - assert user == local_user - end - - test "tries to use the information in poco fields" do - uri = "https://social.heldscal.la/user/23211" - - {:ok, user} = OStatus.find_or_make_user(uri) - - user = User.get_cached_by_id(user.id) - assert user.name == "Constance Variable" - assert user.nickname == "lambadalambda@social.heldscal.la" - assert user.local == false - assert user.info.uri == uri - assert user.ap_id == uri - assert user.bio == "Call me Deacon Blues." - assert user.avatar["type"] == "Image" - - {:ok, user_again} = OStatus.find_or_make_user(uri) - - assert user == user_again - end - - test "find_or_make_user sets all the nessary input fields" do - uri = "https://social.heldscal.la/user/23211" - {:ok, user} = OStatus.find_or_make_user(uri) - - assert user.info == - %User.Info{ - id: user.info.id, - ap_enabled: false, - background: %{}, - banner: %{}, - blocks: [], - deactivated: false, - default_scope: "public", - domain_blocks: [], - follower_count: 0, - is_admin: false, - is_moderator: false, - keys: nil, - locked: false, - no_rich_text: false, - note_count: 0, - settings: nil, - source_data: %{}, - hub: "https://social.heldscal.la/main/push/hub", - magic_key: - "RSA.uzg6r1peZU0vXGADWxGJ0PE34WvmhjUmydbX5YYdOiXfODVLwCMi1umGoqUDm-mRu4vNEdFBVJU1CpFA7dKzWgIsqsa501i2XqElmEveXRLvNRWFB6nG03Q5OUY2as8eE54BJm0p20GkMfIJGwP6TSFb-ICp3QjzbatuSPJ6xCE=.AQAB", - salmon: "https://social.heldscal.la/main/salmon/user/23211", - topic: "https://social.heldscal.la/api/statuses/user_timeline/23211.atom", - uri: "https://social.heldscal.la/user/23211" - } - end - - test "find_make_or_update_actor takes an author element and returns an updated user" do - uri = "https://social.heldscal.la/user/23211" - - {:ok, user} = OStatus.find_or_make_user(uri) - old_name = user.name - old_bio = user.bio - change = Ecto.Changeset.change(user, %{avatar: nil, bio: nil, name: nil}) - - {:ok, user} = Repo.update(change) - refute user.avatar - - doc = XML.parse_document(File.read!("test/fixtures/23211.atom")) - [author] = :xmerl_xpath.string('//author[1]', doc) - {:ok, user} = OStatus.find_make_or_update_actor(author) - assert user.avatar["type"] == "Image" - assert user.name == old_name - assert user.bio == old_bio - - {:ok, user_again} = OStatus.find_make_or_update_actor(author) - assert user_again == user - end - - test "find_or_make_user disallows protocol downgrade" do - user = insert(:user, %{local: true}) - {:ok, user} = OStatus.find_or_make_user(user.ap_id) - - assert User.ap_enabled?(user) - - user = - insert(:user, %{ - ap_id: "https://social.heldscal.la/user/23211", - info: %{ap_enabled: true}, - local: false - }) - - assert User.ap_enabled?(user) - - {:ok, user} = OStatus.find_or_make_user(user.ap_id) - assert User.ap_enabled?(user) - end - - test "find_make_or_update_actor disallows protocol downgrade" do - user = insert(:user, %{local: true}) - {:ok, user} = OStatus.find_or_make_user(user.ap_id) - - assert User.ap_enabled?(user) - - user = - insert(:user, %{ - ap_id: "https://social.heldscal.la/user/23211", - info: %{ap_enabled: true}, - local: false - }) - - assert User.ap_enabled?(user) - - {:ok, user} = OStatus.find_or_make_user(user.ap_id) - assert User.ap_enabled?(user) - - doc = XML.parse_document(File.read!("test/fixtures/23211.atom")) - [author] = :xmerl_xpath.string('//author[1]', doc) - {:error, :invalid_protocol} = OStatus.find_make_or_update_actor(author) - end - end - - describe "gathering user info from a user id" do - test "it returns user info in a hash" do - user = "shp@social.heldscal.la" - - # TODO: make test local - {:ok, data} = OStatus.gather_user_info(user) - - expected = %{ - "hub" => "https://social.heldscal.la/main/push/hub", - "magic_key" => - "RSA.wQ3i9UA0qmAxZ0WTIp4a-waZn_17Ez1pEEmqmqoooRsG1_BvpmOvLN0G2tEcWWxl2KOtdQMCiPptmQObeZeuj48mdsDZ4ArQinexY2hCCTcbV8Xpswpkb8K05RcKipdg07pnI7tAgQ0VWSZDImncL6YUGlG5YN8b5TjGOwk2VG8=.AQAB", - "name" => "shp", - "nickname" => "shp", - "salmon" => "https://social.heldscal.la/main/salmon/user/29191", - "subject" => "acct:shp@social.heldscal.la", - "topic" => "https://social.heldscal.la/api/statuses/user_timeline/29191.atom", - "uri" => "https://social.heldscal.la/user/29191", - "host" => "social.heldscal.la", - "fqn" => user, - "bio" => "cofe", - "avatar" => %{ - "type" => "Image", - "url" => [ - %{ - "href" => "https://social.heldscal.la/avatar/29191-original-20170421154949.jpeg", - "mediaType" => "image/jpeg", - "type" => "Link" - } - ] - }, - "subscribe_address" => "https://social.heldscal.la/main/ostatussub?profile={uri}", - "ap_id" => nil - } - - assert data == expected - end - - test "it works with the uri" do - user = "https://social.heldscal.la/user/29191" - - # TODO: make test local - {:ok, data} = OStatus.gather_user_info(user) - - expected = %{ - "hub" => "https://social.heldscal.la/main/push/hub", - "magic_key" => - "RSA.wQ3i9UA0qmAxZ0WTIp4a-waZn_17Ez1pEEmqmqoooRsG1_BvpmOvLN0G2tEcWWxl2KOtdQMCiPptmQObeZeuj48mdsDZ4ArQinexY2hCCTcbV8Xpswpkb8K05RcKipdg07pnI7tAgQ0VWSZDImncL6YUGlG5YN8b5TjGOwk2VG8=.AQAB", - "name" => "shp", - "nickname" => "shp", - "salmon" => "https://social.heldscal.la/main/salmon/user/29191", - "subject" => "https://social.heldscal.la/user/29191", - "topic" => "https://social.heldscal.la/api/statuses/user_timeline/29191.atom", - "uri" => "https://social.heldscal.la/user/29191", - "host" => "social.heldscal.la", - "fqn" => user, - "bio" => "cofe", - "avatar" => %{ - "type" => "Image", - "url" => [ - %{ - "href" => "https://social.heldscal.la/avatar/29191-original-20170421154949.jpeg", - "mediaType" => "image/jpeg", - "type" => "Link" - } - ] - }, - "subscribe_address" => "https://social.heldscal.la/main/ostatussub?profile={uri}", - "ap_id" => nil - } - - assert data == expected - end - end - - describe "fetching a status by it's HTML url" do - test "it builds a missing status from an html url" do - capture_log(fn -> - url = "https://shitposter.club/notice/2827873" - {:ok, [activity]} = OStatus.fetch_activity_from_url(url) - - assert activity.data["actor"] == "https://shitposter.club/user/1" - - assert activity.data["object"] == - "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment" - end) - end - - test "it works for atom notes, too" do - url = "https://social.sakamoto.gq/objects/0ccc1a2c-66b0-4305-b23a-7f7f2b040056" - {:ok, [activity]} = OStatus.fetch_activity_from_url(url) - assert activity.data["actor"] == "https://social.sakamoto.gq/users/eal" - assert activity.data["object"] == url - end - end - - test "it doesn't add nil in the to field" do - incoming = File.read!("test/fixtures/nil_mention_entry.xml") - {:ok, [activity]} = OStatus.handle_incoming(incoming) - - assert activity.data["to"] == [ - "http://localhost:4001/users/atarifrosch@social.stopwatchingus-heidelberg.de/followers", - "https://www.w3.org/ns/activitystreams#Public" - ] - end - - describe "is_representable?" do - test "Note objects are representable" do - note_activity = insert(:note_activity) - - assert OStatus.is_representable?(note_activity) - end - - test "Article objects are not representable" do - note_activity = insert(:note_activity) - note_object = Object.normalize(note_activity) - - note_data = - note_object.data - |> Map.put("type", "Article") - - Cachex.clear(:object_cache) - - cs = Object.change(note_object, %{data: note_data}) - {:ok, _article_object} = Repo.update(cs) - - # the underlying object is now an Article instead of a note, so this should fail - refute OStatus.is_representable?(note_activity) - end - end -end diff --git a/test/web/ostatus/user_representer_test.exs b/test/web/ostatus/user_representer_test.exs deleted file mode 100644 index e3863d2e9..000000000 --- a/test/web/ostatus/user_representer_test.exs +++ /dev/null @@ -1,38 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OStatus.UserRepresenterTest do - use Pleroma.DataCase - alias Pleroma.Web.OStatus.UserRepresenter - - import Pleroma.Factory - alias Pleroma.User - - test "returns a user with id, uri, name and link" do - user = insert(:user, %{nickname: "レイン"}) - tuple = UserRepresenter.to_simple_form(user) - - res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> to_string - - expected = """ - <id>#{user.ap_id}</id> - <activity:object>http://activitystrea.ms/schema/1.0/person</activity:object> - <uri>#{user.ap_id}</uri> - <poco:preferredUsername>#{user.nickname}</poco:preferredUsername> - <poco:displayName>#{user.name}</poco:displayName> - <poco:note>#{user.bio}</poco:note> - <summary>#{user.bio}</summary> - <name>#{user.nickname}</name> - <link rel="avatar" href="#{User.avatar_url(user)}" /> - <link rel="header" href="#{User.banner_url(user)}" /> - <ap_enabled>true</ap_enabled> - """ - - assert clean(res) == clean(expected) - end - - defp clean(string) do - String.replace(string, ~r/\s/, "") - end -end diff --git a/test/web/pleroma_api/controllers/account_controller_test.exs b/test/web/pleroma_api/controllers/account_controller_test.exs new file mode 100644 index 000000000..245cc1579 --- /dev/null +++ b/test/web/pleroma_api/controllers/account_controller_test.exs @@ -0,0 +1,338 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Config + alias Pleroma.Tests.ObanHelpers + alias Pleroma.User + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + import Swoosh.TestAssertions + + @image "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7" + + describe "POST /api/v1/pleroma/accounts/confirmation_resend" do + setup do + {:ok, user} = + insert(:user) + |> User.confirmation_changeset(need_confirmation: true) + |> User.update_and_set_cache() + + assert user.confirmation_pending + + [user: user] + end + + clear_config([:instance, :account_activation_required]) do + Config.put([:instance, :account_activation_required], true) + end + + test "resend account confirmation email", %{conn: conn, user: user} do + conn + |> post("/api/v1/pleroma/accounts/confirmation_resend?email=#{user.email}") + |> json_response(:no_content) + + ObanHelpers.perform_all() + + email = Pleroma.Emails.UserEmail.account_confirmation_email(user) + notify_email = Config.get([:instance, :notify_email]) + instance_name = Config.get([:instance, :name]) + + assert_email_sent( + from: {instance_name, notify_email}, + to: {user.name, user.email}, + html_body: email.html_body + ) + end + end + + describe "PATCH /api/v1/pleroma/accounts/update_avatar" do + setup do: oauth_access(["write:accounts"]) + + test "user avatar can be set", %{user: user, conn: conn} do + avatar_image = File.read!("test/fixtures/avatar_data_uri") + + conn = patch(conn, "/api/v1/pleroma/accounts/update_avatar", %{img: avatar_image}) + + user = refresh_record(user) + + assert %{ + "name" => _, + "type" => _, + "url" => [ + %{ + "href" => _, + "mediaType" => _, + "type" => _ + } + ] + } = user.avatar + + assert %{"url" => _} = json_response(conn, 200) + end + + test "user avatar can be reset", %{user: user, conn: conn} do + conn = patch(conn, "/api/v1/pleroma/accounts/update_avatar", %{img: ""}) + + user = User.get_cached_by_id(user.id) + + assert user.avatar == nil + + assert %{"url" => nil} = json_response(conn, 200) + end + end + + describe "PATCH /api/v1/pleroma/accounts/update_banner" do + setup do: oauth_access(["write:accounts"]) + + test "can set profile banner", %{user: user, conn: conn} do + conn = patch(conn, "/api/v1/pleroma/accounts/update_banner", %{"banner" => @image}) + + user = refresh_record(user) + assert user.banner["type"] == "Image" + + assert %{"url" => _} = json_response(conn, 200) + end + + test "can reset profile banner", %{user: user, conn: conn} do + conn = patch(conn, "/api/v1/pleroma/accounts/update_banner", %{"banner" => ""}) + + user = refresh_record(user) + assert user.banner == %{} + + assert %{"url" => nil} = json_response(conn, 200) + end + end + + describe "PATCH /api/v1/pleroma/accounts/update_background" do + setup do: oauth_access(["write:accounts"]) + + test "background image can be set", %{user: user, conn: conn} do + conn = patch(conn, "/api/v1/pleroma/accounts/update_background", %{"img" => @image}) + + user = refresh_record(user) + assert user.background["type"] == "Image" + assert %{"url" => _} = json_response(conn, 200) + end + + test "background image can be reset", %{user: user, conn: conn} do + conn = patch(conn, "/api/v1/pleroma/accounts/update_background", %{"img" => ""}) + + user = refresh_record(user) + assert user.background == %{} + assert %{"url" => nil} = json_response(conn, 200) + end + end + + describe "getting favorites timeline of specified user" do + setup do + [current_user, user] = insert_pair(:user, hide_favorites: false) + %{user: current_user, conn: conn} = oauth_access(["read:favourites"], user: current_user) + [current_user: current_user, user: user, conn: conn] + end + + test "returns list of statuses favorited by specified user", %{ + conn: conn, + user: user + } do + [activity | _] = insert_pair(:note_activity) + CommonAPI.favorite(activity.id, user) + + response = + conn + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + [like] = response + + assert length(response) == 1 + assert like["id"] == activity.id + end + + test "does not return favorites for specified user_id when user is not logged in", %{ + user: user + } do + activity = insert(:note_activity) + CommonAPI.favorite(activity.id, user) + + build_conn() + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(403) + end + + test "returns favorited DM only when user is logged in and he is one of recipients", %{ + current_user: current_user, + user: user + } do + {:ok, direct} = + CommonAPI.post(current_user, %{ + "status" => "Hi @#{user.nickname}!", + "visibility" => "direct" + }) + + CommonAPI.favorite(direct.id, user) + + for u <- [user, current_user] do + response = + build_conn() + |> assign(:user, u) + |> assign(:token, insert(:oauth_token, user: u, scopes: ["read:favourites"])) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + assert length(response) == 1 + end + + build_conn() + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(403) + end + + test "does not return others' favorited DM when user is not one of recipients", %{ + conn: conn, + user: user + } do + user_two = insert(:user) + + {:ok, direct} = + CommonAPI.post(user_two, %{ + "status" => "Hi @#{user.nickname}!", + "visibility" => "direct" + }) + + CommonAPI.favorite(direct.id, user) + + response = + conn + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + assert Enum.empty?(response) + end + + test "paginates favorites using since_id and max_id", %{ + conn: conn, + user: user + } do + activities = insert_list(10, :note_activity) + + Enum.each(activities, fn activity -> + CommonAPI.favorite(activity.id, user) + end) + + third_activity = Enum.at(activities, 2) + seventh_activity = Enum.at(activities, 6) + + response = + conn + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{ + since_id: third_activity.id, + max_id: seventh_activity.id + }) + |> json_response(:ok) + + assert length(response) == 3 + refute third_activity in response + refute seventh_activity in response + end + + test "limits favorites using limit parameter", %{ + conn: conn, + user: user + } do + 7 + |> insert_list(:note_activity) + |> Enum.each(fn activity -> + CommonAPI.favorite(activity.id, user) + end) + + response = + conn + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"}) + |> json_response(:ok) + + assert length(response) == 3 + end + + test "returns empty response when user does not have any favorited statuses", %{ + conn: conn, + user: user + } do + response = + conn + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + assert Enum.empty?(response) + end + + test "returns 404 error when specified user is not exist", %{conn: conn} do + conn = get(conn, "/api/v1/pleroma/accounts/test/favourites") + + assert json_response(conn, 404) == %{"error" => "Record not found"} + end + + test "returns 403 error when user has hidden own favorites", %{conn: conn} do + user = insert(:user, hide_favorites: true) + activity = insert(:note_activity) + CommonAPI.favorite(activity.id, user) + + conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/favourites") + + assert json_response(conn, 403) == %{"error" => "Can't get favorites"} + end + + test "hides favorites for new users by default", %{conn: conn} do + user = insert(:user) + activity = insert(:note_activity) + CommonAPI.favorite(activity.id, user) + + assert user.hide_favorites + conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/favourites") + + assert json_response(conn, 403) == %{"error" => "Can't get favorites"} + end + end + + describe "subscribing / unsubscribing" do + test "subscribing / unsubscribing to a user" do + %{user: user, conn: conn} = oauth_access(["follow"]) + subscription_target = insert(:user) + + ret_conn = + conn + |> assign(:user, user) + |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe") + + assert %{"id" => _id, "subscribing" => true} = json_response(ret_conn, 200) + + conn = post(conn, "/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe") + + assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200) + end + end + + describe "subscribing" do + test "returns 404 when subscription_target not found" do + %{conn: conn} = oauth_access(["write:follows"]) + + conn = post(conn, "/api/v1/pleroma/accounts/target_id/subscribe") + + assert %{"error" => "Record not found"} = json_response(conn, 404) + end + end + + describe "unsubscribing" do + test "returns 404 when subscription_target not found" do + %{conn: conn} = oauth_access(["follow"]) + + conn = post(conn, "/api/v1/pleroma/accounts/target_id/unsubscribe") + + assert %{"error" => "Record not found"} = json_response(conn, 404) + end + end +end diff --git a/test/web/pleroma_api/controllers/emoji_api_controller_test.exs b/test/web/pleroma_api/controllers/emoji_api_controller_test.exs new file mode 100644 index 000000000..4b9f5cf9a --- /dev/null +++ b/test/web/pleroma_api/controllers/emoji_api_controller_test.exs @@ -0,0 +1,466 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do + use Pleroma.Web.ConnCase + + import Tesla.Mock + import Pleroma.Factory + + @emoji_dir_path Path.join( + Pleroma.Config.get!([:instance, :static_dir]), + "emoji" + ) + + clear_config([:auth, :enforce_oauth_admin_scope_usage]) do + Pleroma.Config.put([:auth, :enforce_oauth_admin_scope_usage], false) + end + + test "shared & non-shared pack information in list_packs is ok" do + conn = build_conn() + resp = conn |> get(emoji_api_path(conn, :list_packs)) |> json_response(200) + + assert Map.has_key?(resp, "test_pack") + + pack = resp["test_pack"] + + assert Map.has_key?(pack["pack"], "download-sha256") + assert pack["pack"]["can-download"] + + assert pack["files"] == %{"blank" => "blank.png"} + + # Non-shared pack + + assert Map.has_key?(resp, "test_pack_nonshared") + + pack = resp["test_pack_nonshared"] + + refute pack["pack"]["shared"] + refute pack["pack"]["can-download"] + end + + test "listing remote packs" do + admin = insert(:user, is_admin: true) + %{conn: conn} = oauth_access(["admin:write"], user: admin) + + resp = + build_conn() + |> get(emoji_api_path(conn, :list_packs)) + |> json_response(200) + + mock(fn + %{method: :get, url: "https://example.com/.well-known/nodeinfo"} -> + json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]}) + + %{method: :get, url: "https://example.com/nodeinfo/2.1.json"} -> + json(%{metadata: %{features: ["shareable_emoji_packs"]}}) + + %{method: :get, url: "https://example.com/api/pleroma/emoji/packs"} -> + json(resp) + end) + + assert conn + |> post(emoji_api_path(conn, :list_from), %{instance_address: "https://example.com"}) + |> json_response(200) == resp + end + + test "downloading a shared pack from download_shared" do + conn = build_conn() + + resp = + conn + |> get(emoji_api_path(conn, :download_shared, "test_pack")) + |> response(200) + + {:ok, arch} = :zip.unzip(resp, [:memory]) + + assert Enum.find(arch, fn {n, _} -> n == 'pack.json' end) + assert Enum.find(arch, fn {n, _} -> n == 'blank.png' end) + end + + test "downloading shared & unshared packs from another instance via download_from, deleting them" do + on_exit(fn -> + File.rm_rf!("#{@emoji_dir_path}/test_pack2") + File.rm_rf!("#{@emoji_dir_path}/test_pack_nonshared2") + end) + + mock(fn + %{method: :get, url: "https://old-instance/.well-known/nodeinfo"} -> + json(%{links: [%{href: "https://old-instance/nodeinfo/2.1.json"}]}) + + %{method: :get, url: "https://old-instance/nodeinfo/2.1.json"} -> + json(%{metadata: %{features: []}}) + + %{method: :get, url: "https://example.com/.well-known/nodeinfo"} -> + json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]}) + + %{method: :get, url: "https://example.com/nodeinfo/2.1.json"} -> + json(%{metadata: %{features: ["shareable_emoji_packs"]}}) + + %{ + method: :get, + url: "https://example.com/api/pleroma/emoji/packs/list" + } -> + conn = build_conn() + + conn + |> get(emoji_api_path(conn, :list_packs)) + |> json_response(200) + |> json() + + %{ + method: :get, + url: "https://example.com/api/pleroma/emoji/packs/download_shared/test_pack" + } -> + conn = build_conn() + + conn + |> get(emoji_api_path(conn, :download_shared, "test_pack")) + |> response(200) + |> text() + + %{ + method: :get, + url: "https://nonshared-pack" + } -> + text(File.read!("#{@emoji_dir_path}/test_pack_nonshared/nonshared.zip")) + end) + + admin = insert(:user, is_admin: true) + + conn = + build_conn() + |> assign(:user, admin) + |> assign(:token, insert(:oauth_admin_token, user: admin, scopes: ["admin:write"])) + + assert (conn + |> put_req_header("content-type", "application/json") + |> post( + emoji_api_path( + conn, + :download_from + ), + %{ + instance_address: "https://old-instance", + pack_name: "test_pack", + as: "test_pack2" + } + |> Jason.encode!() + ) + |> json_response(500))["error"] =~ "does not support" + + assert conn + |> put_req_header("content-type", "application/json") + |> post( + emoji_api_path( + conn, + :download_from + ), + %{ + instance_address: "https://example.com", + pack_name: "test_pack", + as: "test_pack2" + } + |> Jason.encode!() + ) + |> json_response(200) == "ok" + + assert File.exists?("#{@emoji_dir_path}/test_pack2/pack.json") + assert File.exists?("#{@emoji_dir_path}/test_pack2/blank.png") + + assert conn + |> delete(emoji_api_path(conn, :delete, "test_pack2")) + |> json_response(200) == "ok" + + refute File.exists?("#{@emoji_dir_path}/test_pack2") + + # non-shared, downloaded from the fallback URL + + assert conn + |> put_req_header("content-type", "application/json") + |> post( + emoji_api_path( + conn, + :download_from + ), + %{ + instance_address: "https://example.com", + pack_name: "test_pack_nonshared", + as: "test_pack_nonshared2" + } + |> Jason.encode!() + ) + |> json_response(200) == "ok" + + assert File.exists?("#{@emoji_dir_path}/test_pack_nonshared2/pack.json") + assert File.exists?("#{@emoji_dir_path}/test_pack_nonshared2/blank.png") + + assert conn + |> delete(emoji_api_path(conn, :delete, "test_pack_nonshared2")) + |> json_response(200) == "ok" + + refute File.exists?("#{@emoji_dir_path}/test_pack_nonshared2") + end + + describe "updating pack metadata" do + setup do + pack_file = "#{@emoji_dir_path}/test_pack/pack.json" + original_content = File.read!(pack_file) + + on_exit(fn -> + File.write!(pack_file, original_content) + end) + + admin = insert(:user, is_admin: true) + %{conn: conn} = oauth_access(["admin:write"], user: admin) + + {:ok, + admin: admin, + conn: conn, + pack_file: pack_file, + new_data: %{ + "license" => "Test license changed", + "homepage" => "https://pleroma.social", + "description" => "Test description", + "share-files" => false + }} + end + + test "for a pack without a fallback source", ctx do + conn = ctx[:conn] + + assert conn + |> post( + emoji_api_path(conn, :update_metadata, "test_pack"), + %{ + "new_data" => ctx[:new_data] + } + ) + |> json_response(200) == ctx[:new_data] + + assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == ctx[:new_data] + end + + test "for a pack with a fallback source", ctx do + mock(fn + %{ + method: :get, + url: "https://nonshared-pack" + } -> + text(File.read!("#{@emoji_dir_path}/test_pack_nonshared/nonshared.zip")) + end) + + new_data = Map.put(ctx[:new_data], "fallback-src", "https://nonshared-pack") + + new_data_with_sha = + Map.put( + new_data, + "fallback-src-sha256", + "74409E2674DAA06C072729C6C8426C4CB3B7E0B85ED77792DB7A436E11D76DAF" + ) + + conn = ctx[:conn] + + assert conn + |> post( + emoji_api_path(conn, :update_metadata, "test_pack"), + %{ + "new_data" => new_data + } + ) + |> json_response(200) == new_data_with_sha + + assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == new_data_with_sha + end + + test "when the fallback source doesn't have all the files", ctx do + mock(fn + %{ + method: :get, + url: "https://nonshared-pack" + } -> + {:ok, {'empty.zip', empty_arch}} = :zip.zip('empty.zip', [], [:memory]) + text(empty_arch) + end) + + new_data = Map.put(ctx[:new_data], "fallback-src", "https://nonshared-pack") + + conn = ctx[:conn] + + assert (conn + |> post( + emoji_api_path(conn, :update_metadata, "test_pack"), + %{ + "new_data" => new_data + } + ) + |> json_response(:bad_request))["error"] =~ "does not have all" + end + end + + test "updating pack files" do + pack_file = "#{@emoji_dir_path}/test_pack/pack.json" + original_content = File.read!(pack_file) + + on_exit(fn -> + File.write!(pack_file, original_content) + + File.rm_rf!("#{@emoji_dir_path}/test_pack/blank_url.png") + File.rm_rf!("#{@emoji_dir_path}/test_pack/dir") + File.rm_rf!("#{@emoji_dir_path}/test_pack/dir_2") + end) + + admin = insert(:user, is_admin: true) + %{conn: conn} = oauth_access(["admin:write"], user: admin) + + same_name = %{ + "action" => "add", + "shortcode" => "blank", + "filename" => "dir/blank.png", + "file" => %Plug.Upload{ + filename: "blank.png", + path: "#{@emoji_dir_path}/test_pack/blank.png" + } + } + + different_name = %{same_name | "shortcode" => "blank_2"} + + assert (conn + |> post(emoji_api_path(conn, :update_file, "test_pack"), same_name) + |> json_response(:conflict))["error"] =~ "already exists" + + assert conn + |> post(emoji_api_path(conn, :update_file, "test_pack"), different_name) + |> json_response(200) == %{"blank" => "blank.png", "blank_2" => "dir/blank.png"} + + assert File.exists?("#{@emoji_dir_path}/test_pack/dir/blank.png") + + assert conn + |> post(emoji_api_path(conn, :update_file, "test_pack"), %{ + "action" => "update", + "shortcode" => "blank_2", + "new_shortcode" => "blank_3", + "new_filename" => "dir_2/blank_3.png" + }) + |> json_response(200) == %{"blank" => "blank.png", "blank_3" => "dir_2/blank_3.png"} + + refute File.exists?("#{@emoji_dir_path}/test_pack/dir/") + assert File.exists?("#{@emoji_dir_path}/test_pack/dir_2/blank_3.png") + + assert conn + |> post(emoji_api_path(conn, :update_file, "test_pack"), %{ + "action" => "remove", + "shortcode" => "blank_3" + }) + |> json_response(200) == %{"blank" => "blank.png"} + + refute File.exists?("#{@emoji_dir_path}/test_pack/dir_2/") + + mock(fn + %{ + method: :get, + url: "https://test-blank/blank_url.png" + } -> + text(File.read!("#{@emoji_dir_path}/test_pack/blank.png")) + end) + + # The name should be inferred from the URL ending + from_url = %{ + "action" => "add", + "shortcode" => "blank_url", + "file" => "https://test-blank/blank_url.png" + } + + assert conn + |> post(emoji_api_path(conn, :update_file, "test_pack"), from_url) + |> json_response(200) == %{ + "blank" => "blank.png", + "blank_url" => "blank_url.png" + } + + assert File.exists?("#{@emoji_dir_path}/test_pack/blank_url.png") + + assert conn + |> post(emoji_api_path(conn, :update_file, "test_pack"), %{ + "action" => "remove", + "shortcode" => "blank_url" + }) + |> json_response(200) == %{"blank" => "blank.png"} + + refute File.exists?("#{@emoji_dir_path}/test_pack/blank_url.png") + end + + test "creating and deleting a pack" do + on_exit(fn -> + File.rm_rf!("#{@emoji_dir_path}/test_created") + end) + + admin = insert(:user, is_admin: true) + %{conn: conn} = oauth_access(["admin:write"], user: admin) + + assert conn + |> put_req_header("content-type", "application/json") + |> put( + emoji_api_path( + conn, + :create, + "test_created" + ) + ) + |> json_response(200) == "ok" + + assert File.exists?("#{@emoji_dir_path}/test_created/pack.json") + + assert Jason.decode!(File.read!("#{@emoji_dir_path}/test_created/pack.json")) == %{ + "pack" => %{}, + "files" => %{} + } + + assert conn + |> delete(emoji_api_path(conn, :delete, "test_created")) + |> json_response(200) == "ok" + + refute File.exists?("#{@emoji_dir_path}/test_created/pack.json") + end + + test "filesystem import" do + on_exit(fn -> + File.rm!("#{@emoji_dir_path}/test_pack_for_import/emoji.txt") + File.rm!("#{@emoji_dir_path}/test_pack_for_import/pack.json") + end) + + conn = build_conn() + resp = conn |> get(emoji_api_path(conn, :list_packs)) |> json_response(200) + + refute Map.has_key?(resp, "test_pack_for_import") + + admin = insert(:user, is_admin: true) + %{conn: conn} = oauth_access(["admin:write"], user: admin) + + assert conn + |> post(emoji_api_path(conn, :import_from_fs)) + |> json_response(200) == ["test_pack_for_import"] + + resp = conn |> get(emoji_api_path(conn, :list_packs)) |> json_response(200) + assert resp["test_pack_for_import"]["files"] == %{"blank" => "blank.png"} + + File.rm!("#{@emoji_dir_path}/test_pack_for_import/pack.json") + refute File.exists?("#{@emoji_dir_path}/test_pack_for_import/pack.json") + + emoji_txt_content = "blank, blank.png, Fun\n\nblank2, blank.png" + + File.write!("#{@emoji_dir_path}/test_pack_for_import/emoji.txt", emoji_txt_content) + + assert conn + |> post(emoji_api_path(conn, :import_from_fs)) + |> json_response(200) == ["test_pack_for_import"] + + resp = build_conn() |> get(emoji_api_path(conn, :list_packs)) |> json_response(200) + + assert resp["test_pack_for_import"]["files"] == %{ + "blank" => "blank.png", + "blank2" => "blank.png" + } + end +end diff --git a/test/web/pleroma_api/controllers/mascot_controller_test.exs b/test/web/pleroma_api/controllers/mascot_controller_test.exs new file mode 100644 index 000000000..617831b02 --- /dev/null +++ b/test/web/pleroma_api/controllers/mascot_controller_test.exs @@ -0,0 +1,64 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.MascotControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.User + + test "mascot upload" do + %{conn: conn} = oauth_access(["write:accounts"]) + + non_image_file = %Plug.Upload{ + content_type: "audio/mpeg", + path: Path.absname("test/fixtures/sound.mp3"), + filename: "sound.mp3" + } + + ret_conn = put(conn, "/api/v1/pleroma/mascot", %{"file" => non_image_file}) + + assert json_response(ret_conn, 415) + + file = %Plug.Upload{ + content_type: "image/jpg", + path: Path.absname("test/fixtures/image.jpg"), + filename: "an_image.jpg" + } + + conn = put(conn, "/api/v1/pleroma/mascot", %{"file" => file}) + + assert %{"id" => _, "type" => image} = json_response(conn, 200) + end + + test "mascot retrieving" do + %{user: user, conn: conn} = oauth_access(["read:accounts", "write:accounts"]) + + # When user hasn't set a mascot, we should just get pleroma tan back + ret_conn = get(conn, "/api/v1/pleroma/mascot") + + assert %{"url" => url} = json_response(ret_conn, 200) + assert url =~ "pleroma-fox-tan-smol" + + # When a user sets their mascot, we should get that back + file = %Plug.Upload{ + content_type: "image/jpg", + path: Path.absname("test/fixtures/image.jpg"), + filename: "an_image.jpg" + } + + ret_conn = put(conn, "/api/v1/pleroma/mascot", %{"file" => file}) + + assert json_response(ret_conn, 200) + + user = User.get_cached_by_id(user.id) + + conn = + conn + |> assign(:user, user) + |> get("/api/v1/pleroma/mascot") + + assert %{"url" => url, "type" => "image"} = json_response(conn, 200) + assert url =~ "an_image" + end +end diff --git a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs new file mode 100644 index 000000000..32250f06f --- /dev/null +++ b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs @@ -0,0 +1,279 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Conversation.Participation + alias Pleroma.Notification + alias Pleroma.Object + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + test "PUT /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) + + result = + conn + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"])) + |> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/☕") + |> json_response(200) + + # We return the status, but this our implementation detail. + assert %{"id" => id} = result + assert to_string(activity.id) == id + + assert result["pleroma"]["emoji_reactions"] == [ + %{"name" => "☕", "count" => 1, "me" => true} + ] + end + + test "DELETE /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) + {:ok, activity, _object} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") + + result = + conn + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"])) + |> delete("/api/v1/pleroma/statuses/#{activity.id}/reactions/☕") + + assert %{"id" => id} = json_response(result, 200) + assert to_string(activity.id) == id + + object = Object.normalize(activity) + + assert object.data["reaction_count"] == 0 + end + + test "GET /api/v1/pleroma/statuses/:id/reactions", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + doomed_user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) + + result = + conn + |> get("/api/v1/pleroma/statuses/#{activity.id}/reactions") + |> json_response(200) + + assert result == [] + + {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅") + {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, doomed_user, "🎅") + + User.perform(:delete, doomed_user) + + result = + conn + |> get("/api/v1/pleroma/statuses/#{activity.id}/reactions") + |> json_response(200) + + [%{"name" => "🎅", "count" => 1, "accounts" => [represented_user], "me" => false}] = result + + assert represented_user["id"] == other_user.id + + result = + conn + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:statuses"])) + |> get("/api/v1/pleroma/statuses/#{activity.id}/reactions") + |> json_response(200) + + assert [%{"name" => "🎅", "count" => 1, "accounts" => [_represented_user], "me" => true}] = + result + end + + test "GET /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) + + result = + conn + |> get("/api/v1/pleroma/statuses/#{activity.id}/reactions/🎅") + |> json_response(200) + + assert result == [] + + {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅") + {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") + + result = + conn + |> get("/api/v1/pleroma/statuses/#{activity.id}/reactions/🎅") + |> json_response(200) + + [%{"name" => "🎅", "count" => 1, "accounts" => [represented_user], "me" => false}] = result + + assert represented_user["id"] == other_user.id + end + + test "/api/v1/pleroma/conversations/:id" do + user = insert(:user) + %{user: other_user, conn: conn} = oauth_access(["read:statuses"]) + + {:ok, _activity} = + CommonAPI.post(user, %{"status" => "Hi @#{other_user.nickname}!", "visibility" => "direct"}) + + [participation] = Participation.for_user(other_user) + + result = + conn + |> get("/api/v1/pleroma/conversations/#{participation.id}") + |> json_response(200) + + assert result["id"] == participation.id |> to_string() + end + + test "/api/v1/pleroma/conversations/:id/statuses" do + user = insert(:user) + %{user: other_user, conn: conn} = oauth_access(["read:statuses"]) + third_user = insert(:user) + + {:ok, _activity} = + CommonAPI.post(user, %{"status" => "Hi @#{third_user.nickname}!", "visibility" => "direct"}) + + {:ok, activity} = + CommonAPI.post(user, %{"status" => "Hi @#{other_user.nickname}!", "visibility" => "direct"}) + + [participation] = Participation.for_user(other_user) + + {:ok, activity_two} = + CommonAPI.post(other_user, %{ + "status" => "Hi!", + "in_reply_to_status_id" => activity.id, + "in_reply_to_conversation_id" => participation.id + }) + + result = + conn + |> get("/api/v1/pleroma/conversations/#{participation.id}/statuses") + |> json_response(200) + + assert length(result) == 2 + + id_one = activity.id + id_two = activity_two.id + assert [%{"id" => ^id_one}, %{"id" => ^id_two}] = result + end + + test "PATCH /api/v1/pleroma/conversations/:id" do + %{user: user, conn: conn} = oauth_access(["write:conversations"]) + other_user = insert(:user) + + {:ok, _activity} = CommonAPI.post(user, %{"status" => "Hi", "visibility" => "direct"}) + + [participation] = Participation.for_user(user) + + participation = Repo.preload(participation, :recipients) + + user = User.get_cached_by_id(user.id) + assert [user] == participation.recipients + assert other_user not in participation.recipients + + result = + conn + |> patch("/api/v1/pleroma/conversations/#{participation.id}", %{ + "recipients" => [user.id, other_user.id] + }) + |> json_response(200) + + assert result["id"] == participation.id |> to_string + + [participation] = Participation.for_user(user) + participation = Repo.preload(participation, :recipients) + + assert user in participation.recipients + assert other_user in participation.recipients + end + + test "POST /api/v1/pleroma/conversations/read" do + user = insert(:user) + %{user: other_user, conn: conn} = oauth_access(["write:notifications"]) + + {:ok, _activity} = + CommonAPI.post(user, %{"status" => "Hi @#{other_user.nickname}", "visibility" => "direct"}) + + {:ok, _activity} = + CommonAPI.post(user, %{"status" => "Hi @#{other_user.nickname}", "visibility" => "direct"}) + + [participation2, participation1] = Participation.for_user(other_user) + assert Participation.get(participation2.id).read == false + assert Participation.get(participation1.id).read == false + assert User.get_cached_by_id(other_user.id).unread_conversation_count == 2 + + [%{"unread" => false}, %{"unread" => false}] = + conn + |> post("/api/v1/pleroma/conversations/read", %{}) + |> json_response(200) + + [participation2, participation1] = Participation.for_user(other_user) + assert Participation.get(participation2.id).read == true + assert Participation.get(participation1.id).read == true + assert User.get_cached_by_id(other_user.id).unread_conversation_count == 0 + end + + describe "POST /api/v1/pleroma/notifications/read" do + setup do: oauth_access(["write:notifications"]) + + test "it marks a single notification as read", %{user: user1, conn: conn} do + user2 = insert(:user) + {:ok, activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) + {:ok, activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) + {:ok, [notification1]} = Notification.create_notifications(activity1) + {:ok, [notification2]} = Notification.create_notifications(activity2) + + response = + conn + |> post("/api/v1/pleroma/notifications/read", %{"id" => "#{notification1.id}"}) + |> json_response(:ok) + + assert %{"pleroma" => %{"is_seen" => true}} = response + assert Repo.get(Notification, notification1.id).seen + refute Repo.get(Notification, notification2.id).seen + end + + test "it marks multiple notifications as read", %{user: user1, conn: conn} do + user2 = insert(:user) + {:ok, _activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) + {:ok, _activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) + {:ok, _activity3} = CommonAPI.post(user2, %{"status" => "HIE @#{user1.nickname}"}) + + [notification3, notification2, notification1] = Notification.for_user(user1, %{limit: 3}) + + [response1, response2] = + conn + |> post("/api/v1/pleroma/notifications/read", %{"max_id" => "#{notification2.id}"}) + |> json_response(:ok) + + assert %{"pleroma" => %{"is_seen" => true}} = response1 + assert %{"pleroma" => %{"is_seen" => true}} = response2 + assert Repo.get(Notification, notification1.id).seen + assert Repo.get(Notification, notification2.id).seen + refute Repo.get(Notification, notification3.id).seen + end + + test "it returns error when notification not found", %{conn: conn} do + response = + conn + |> post("/api/v1/pleroma/notifications/read", %{"id" => "22222222222222"}) + |> json_response(:bad_request) + + assert response == %{"error" => "Cannot get notification"} + end + end +end diff --git a/test/web/pleroma_api/controllers/scrobble_controller_test.exs b/test/web/pleroma_api/controllers/scrobble_controller_test.exs new file mode 100644 index 000000000..1b945040c --- /dev/null +++ b/test/web/pleroma_api/controllers/scrobble_controller_test.exs @@ -0,0 +1,58 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.ScrobbleControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Web.CommonAPI + + describe "POST /api/v1/pleroma/scrobble" do + test "works correctly" do + %{conn: conn} = oauth_access(["write"]) + + conn = + post(conn, "/api/v1/pleroma/scrobble", %{ + "title" => "lain radio episode 1", + "artist" => "lain", + "album" => "lain radio", + "length" => "180000" + }) + + assert %{"title" => "lain radio episode 1"} = json_response(conn, 200) + end + end + + describe "GET /api/v1/pleroma/accounts/:id/scrobbles" do + test "works correctly" do + %{user: user, conn: conn} = oauth_access(["read"]) + + {:ok, _activity} = + CommonAPI.listen(user, %{ + "title" => "lain radio episode 1", + "artist" => "lain", + "album" => "lain radio" + }) + + {:ok, _activity} = + CommonAPI.listen(user, %{ + "title" => "lain radio episode 2", + "artist" => "lain", + "album" => "lain radio" + }) + + {:ok, _activity} = + CommonAPI.listen(user, %{ + "title" => "lain radio episode 3", + "artist" => "lain", + "album" => "lain radio" + }) + + conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/scrobbles") + + result = json_response(conn, 200) + + assert length(result) == 3 + end + end +end diff --git a/test/web/pleroma_api/pleroma_api_controller_test.exs b/test/web/pleroma_api/pleroma_api_controller_test.exs deleted file mode 100644 index 7eaeda4a0..000000000 --- a/test/web/pleroma_api/pleroma_api_controller_test.exs +++ /dev/null @@ -1,150 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do - use Pleroma.Web.ConnCase - - alias Pleroma.Conversation.Participation - alias Pleroma.Notification - alias Pleroma.Repo - alias Pleroma.Web.CommonAPI - - import Pleroma.Factory - - test "/api/v1/pleroma/conversations/:id", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, _activity} = - CommonAPI.post(user, %{"status" => "Hi @#{other_user.nickname}!", "visibility" => "direct"}) - - [participation] = Participation.for_user(other_user) - - result = - conn - |> assign(:user, other_user) - |> get("/api/v1/pleroma/conversations/#{participation.id}") - |> json_response(200) - - assert result["id"] == participation.id |> to_string() - end - - test "/api/v1/pleroma/conversations/:id/statuses", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - third_user = insert(:user) - - {:ok, _activity} = - CommonAPI.post(user, %{"status" => "Hi @#{third_user.nickname}!", "visibility" => "direct"}) - - {:ok, activity} = - CommonAPI.post(user, %{"status" => "Hi @#{other_user.nickname}!", "visibility" => "direct"}) - - [participation] = Participation.for_user(other_user) - - {:ok, activity_two} = - CommonAPI.post(other_user, %{ - "status" => "Hi!", - "in_reply_to_status_id" => activity.id, - "in_reply_to_conversation_id" => participation.id - }) - - result = - conn - |> assign(:user, other_user) - |> get("/api/v1/pleroma/conversations/#{participation.id}/statuses") - |> json_response(200) - - assert length(result) == 2 - - id_one = activity.id - id_two = activity_two.id - assert [%{"id" => ^id_one}, %{"id" => ^id_two}] = result - end - - test "PATCH /api/v1/pleroma/conversations/:id", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, _activity} = CommonAPI.post(user, %{"status" => "Hi", "visibility" => "direct"}) - - [participation] = Participation.for_user(user) - - participation = Repo.preload(participation, :recipients) - - assert [user] == participation.recipients - assert other_user not in participation.recipients - - result = - conn - |> assign(:user, user) - |> patch("/api/v1/pleroma/conversations/#{participation.id}", %{ - "recipients" => [user.id, other_user.id] - }) - |> json_response(200) - - assert result["id"] == participation.id |> to_string - - [participation] = Participation.for_user(user) - participation = Repo.preload(participation, :recipients) - - assert user in participation.recipients - assert other_user in participation.recipients - end - - describe "POST /api/v1/pleroma/notifications/read" do - test "it marks a single notification as read", %{conn: conn} do - user1 = insert(:user) - user2 = insert(:user) - {:ok, activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) - {:ok, activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) - {:ok, [notification1]} = Notification.create_notifications(activity1) - {:ok, [notification2]} = Notification.create_notifications(activity2) - - response = - conn - |> assign(:user, user1) - |> post("/api/v1/pleroma/notifications/read", %{"id" => "#{notification1.id}"}) - |> json_response(:ok) - - assert %{"pleroma" => %{"is_seen" => true}} = response - assert Repo.get(Notification, notification1.id).seen - refute Repo.get(Notification, notification2.id).seen - end - - test "it marks multiple notifications as read", %{conn: conn} do - user1 = insert(:user) - user2 = insert(:user) - {:ok, _activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) - {:ok, _activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) - {:ok, _activity3} = CommonAPI.post(user2, %{"status" => "HIE @#{user1.nickname}"}) - - [notification3, notification2, notification1] = Notification.for_user(user1, %{limit: 3}) - - [response1, response2] = - conn - |> assign(:user, user1) - |> post("/api/v1/pleroma/notifications/read", %{"max_id" => "#{notification2.id}"}) - |> json_response(:ok) - - assert %{"pleroma" => %{"is_seen" => true}} = response1 - assert %{"pleroma" => %{"is_seen" => true}} = response2 - assert Repo.get(Notification, notification1.id).seen - assert Repo.get(Notification, notification2.id).seen - refute Repo.get(Notification, notification3.id).seen - end - - test "it returns error when notification not found", %{conn: conn} do - user1 = insert(:user) - - response = - conn - |> assign(:user, user1) - |> post("/api/v1/pleroma/notifications/read", %{"id" => "22222222222222"}) - |> json_response(:bad_request) - - assert response == %{"error" => "Cannot get notification"} - end - end -end diff --git a/test/web/plugs/federating_plug_test.exs b/test/web/plugs/federating_plug_test.exs index bb2e1687a..13edc4359 100644 --- a/test/web/plugs/federating_plug_test.exs +++ b/test/web/plugs/federating_plug_test.exs @@ -1,10 +1,11 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.FederatingPlugTest do use Pleroma.Web.ConnCase - clear_config_all([:instance, :federating]) + + clear_config([:instance, :federating]) test "returns and halt the conn when federating is disabled" do Pleroma.Config.put([:instance, :federating], false) diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs index e2f89f40a..089d55577 100644 --- a/test/web/push/impl_test.exs +++ b/test/web/push/impl_test.exs @@ -1,11 +1,12 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Push.ImplTest do use Pleroma.DataCase alias Pleroma.Object + alias Pleroma.User alias Pleroma.Web.CommonAPI alias Pleroma.Web.Push.Impl alias Pleroma.Web.Push.Subscription @@ -84,7 +85,7 @@ defmodule Pleroma.Web.Push.ImplTest do ) == :error end - test "delete subsciption if restult send message between 400..500" do + test "delete subscription if result send message between 400..500" do subscription = insert(:push_subscription) assert Impl.push_message( @@ -97,7 +98,15 @@ defmodule Pleroma.Web.Push.ImplTest do refute Pleroma.Repo.get(Subscription, subscription.id) end - test "renders body for create activity" do + test "deletes subscription when token has been deleted" do + subscription = insert(:push_subscription) + + Pleroma.Repo.delete(subscription.token) + + refute Pleroma.Repo.get(Subscription, subscription.id) + end + + test "renders title and body for create activity" do user = insert(:user, nickname: "Bob") {:ok, activity} = @@ -116,18 +125,24 @@ defmodule Pleroma.Web.Push.ImplTest do object ) == "@Bob: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sagittis fini..." + + assert Impl.format_title(%{activity: activity}) == + "New Mention" end - test "renders body for follow activity" do + test "renders title and body for follow activity" do user = insert(:user, nickname: "Bob") other_user = insert(:user) {:ok, _, _, activity} = CommonAPI.follow(user, other_user) object = Object.normalize(activity) assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has followed you" + + assert Impl.format_title(%{activity: activity}) == + "New Follower" end - test "renders body for announce activity" do + test "renders title and body for announce activity" do user = insert(:user) {:ok, activity} = @@ -141,9 +156,12 @@ defmodule Pleroma.Web.Push.ImplTest do assert Impl.format_body(%{activity: announce_activity}, user, object) == "@#{user.nickname} repeated: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sagittis fini..." + + assert Impl.format_title(%{activity: announce_activity}) == + "New Repeat" end - test "renders body for like activity" do + test "renders title and body for like activity" do user = insert(:user, nickname: "Bob") {:ok, activity} = @@ -156,5 +174,67 @@ defmodule Pleroma.Web.Push.ImplTest do object = Object.normalize(activity) assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has favorited your post" + + assert Impl.format_title(%{activity: activity}) == + "New Favorite" + end + + test "renders title for create activity with direct visibility" do + user = insert(:user, nickname: "Bob") + + {:ok, activity} = + CommonAPI.post(user, %{ + "visibility" => "direct", + "status" => "This is just between you and me, pal" + }) + + assert Impl.format_title(%{activity: activity}) == + "New Direct Message" + end + + describe "build_content/3" do + test "returns info content for direct message with enabled privacy option" do + user = insert(:user, nickname: "Bob") + user2 = insert(:user, nickname: "Rob", notification_settings: %{privacy_option: true}) + + {:ok, activity} = + CommonAPI.post(user, %{ + "visibility" => "direct", + "status" => "<Lorem ipsum dolor sit amet." + }) + + notif = insert(:notification, user: user2, activity: activity) + + actor = User.get_cached_by_ap_id(notif.activity.data["actor"]) + object = Object.normalize(activity) + + assert Impl.build_content(notif, actor, object) == %{ + body: "@Bob", + title: "New Direct Message" + } + end + + test "returns regular content for direct message with disabled privacy option" do + user = insert(:user, nickname: "Bob") + user2 = insert(:user, nickname: "Rob", notification_settings: %{privacy_option: false}) + + {:ok, activity} = + CommonAPI.post(user, %{ + "visibility" => "direct", + "status" => + "<span>Lorem ipsum dolor sit amet</span>, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis." + }) + + notif = insert(:notification, user: user2, activity: activity) + + actor = User.get_cached_by_ap_id(notif.activity.data["actor"]) + object = Object.normalize(activity) + + assert Impl.build_content(notif, actor, object) == %{ + body: + "@Bob: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sagittis fini...", + title: "New Direct Message" + } + end end end diff --git a/test/web/rel_me_test.exs b/test/web/rel_me_test.exs index 2251fed16..e05a8863d 100644 --- a/test/web/rel_me_test.exs +++ b/test/web/rel_me_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.RelMeTest do @@ -14,7 +14,9 @@ defmodule Pleroma.Web.RelMeTest do hrefs = ["https://social.example.org/users/lain"] assert Pleroma.Web.RelMe.parse("http://example.com/rel_me/null") == {:ok, []} - assert {:error, _} = Pleroma.Web.RelMe.parse("http://example.com/rel_me/error") + + assert {:ok, %Tesla.Env{status: 404}} = + Pleroma.Web.RelMe.parse("http://example.com/rel_me/error") assert Pleroma.Web.RelMe.parse("http://example.com/rel_me/link") == {:ok, hrefs} assert Pleroma.Web.RelMe.parse("http://example.com/rel_me/anchor") == {:ok, hrefs} diff --git a/test/web/retry_queue_test.exs b/test/web/retry_queue_test.exs deleted file mode 100644 index ecb3ce5d0..000000000 --- a/test/web/retry_queue_test.exs +++ /dev/null @@ -1,48 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule MockActivityPub do - def publish_one({ret, waiter}) do - send(waiter, :complete) - {ret, "success"} - end -end - -defmodule Pleroma.Web.Federator.RetryQueueTest do - use Pleroma.DataCase - alias Pleroma.Web.Federator.RetryQueue - - @small_retry_count 0 - @hopeless_retry_count 10 - - setup do - RetryQueue.reset_stats() - end - - test "RetryQueue responds to stats request" do - assert %{delivered: 0, dropped: 0} == RetryQueue.get_stats() - end - - test "failed posts are retried" do - {:retry, _timeout} = RetryQueue.get_retry_params(@small_retry_count) - - wait_task = - Task.async(fn -> - receive do - :complete -> :ok - end - end) - - RetryQueue.enqueue({:ok, wait_task.pid}, MockActivityPub, @small_retry_count) - Task.await(wait_task) - assert %{delivered: 1, dropped: 0} == RetryQueue.get_stats() - end - - test "posts that have been tried too many times are dropped" do - {:drop, _timeout} = RetryQueue.get_retry_params(@hopeless_retry_count) - - RetryQueue.enqueue({:ok, nil}, MockActivityPub, @hopeless_retry_count) - assert %{delivered: 0, dropped: 1} == RetryQueue.get_stats() - end -end diff --git a/test/web/rich_media/aws_signed_url_test.exs b/test/web/rich_media/aws_signed_url_test.exs index a3a50cbb1..b30f4400e 100644 --- a/test/web/rich_media/aws_signed_url_test.exs +++ b/test/web/rich_media/aws_signed_url_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.RichMedia.TTL.AwsSignedUrlTest do diff --git a/test/web/rich_media/helpers_test.exs b/test/web/rich_media/helpers_test.exs index 48884319d..8237802a7 100644 --- a/test/web/rich_media/helpers_test.exs +++ b/test/web/rich_media/helpers_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.RichMedia.HelpersTest do diff --git a/test/web/rich_media/parser_test.exs b/test/web/rich_media/parser_test.exs index b75bdf96f..e54a13bc8 100644 --- a/test/web/rich_media/parser_test.exs +++ b/test/web/rich_media/parser_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.RichMedia.ParserTest do diff --git a/test/web/rich_media/parsers/twitter_card_test.exs b/test/web/rich_media/parsers/twitter_card_test.exs index f8e1c9b40..87c767c15 100644 --- a/test/web/rich_media/parsers/twitter_card_test.exs +++ b/test/web/rich_media/parsers/twitter_card_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do @@ -7,11 +7,14 @@ defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do alias Pleroma.Web.RichMedia.Parsers.TwitterCard test "returns error when html not contains twitter card" do - assert TwitterCard.parse("", %{}) == {:error, "No twitter card metadata found"} + assert TwitterCard.parse([{"html", [], [{"head", [], []}, {"body", [], []}]}], %{}) == + {:error, "No twitter card metadata found"} end test "parses twitter card with only name attributes" do - html = File.read!("test/fixtures/nypd-facial-recognition-children-teenagers3.html") + html = + File.read!("test/fixtures/nypd-facial-recognition-children-teenagers3.html") + |> Floki.parse_document!() assert TwitterCard.parse(html, %{}) == {:ok, @@ -26,7 +29,9 @@ defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do end test "parses twitter card with only property attributes" do - html = File.read!("test/fixtures/nypd-facial-recognition-children-teenagers2.html") + html = + File.read!("test/fixtures/nypd-facial-recognition-children-teenagers2.html") + |> Floki.parse_document!() assert TwitterCard.parse(html, %{}) == {:ok, @@ -45,7 +50,9 @@ defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do end test "parses twitter card with name & property attributes" do - html = File.read!("test/fixtures/nypd-facial-recognition-children-teenagers.html") + html = + File.read!("test/fixtures/nypd-facial-recognition-children-teenagers.html") + |> Floki.parse_document!() assert TwitterCard.parse(html, %{}) == {:ok, @@ -66,4 +73,41 @@ defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do "https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html" }} end + + test "respect only first title tag on the page" do + image_path = + "https://assets.atlasobscura.com/media/W1siZiIsInVwbG9hZHMvYXNzZXRzLzkwYzgyMzI4LThlMDUtNGRiNS05MDg3LTUzMGUxZTM5N2RmMmVkOTM5ZDM4MGM4OTIx" <> + "YTQ5MF9EQVIgZXhodW1hdGlvbiBvZiBNYXJnYXJldCBDb3JiaW4gZ3JhdmUgMTkyNi5qcGciXSxbInAiLCJjb252ZXJ0IiwiIl0sWyJwIiwiY29udmVydCIsIi1xdWFsaXR5IDgxIC1hdXRvLW9" <> + "yaWVudCJdLFsicCIsInRodW1iIiwiNjAweD4iXV0/DAR%20exhumation%20of%20Margaret%20Corbin%20grave%201926.jpg" + + html = + File.read!("test/fixtures/margaret-corbin-grave-west-point.html") |> Floki.parse_document!() + + assert TwitterCard.parse(html, %{}) == + {:ok, + %{ + site: "@atlasobscura", + title: + "The Missing Grave of Margaret Corbin, Revolutionary War Veteran - Atlas Obscura", + card: "summary_large_image", + image: image_path + }} + end + + test "takes first founded title in html head if there is html markup error" do + html = + File.read!("test/fixtures/nypd-facial-recognition-children-teenagers4.html") + |> Floki.parse_document!() + + assert TwitterCard.parse(html, %{}) == + {:ok, + %{ + site: nil, + title: + "She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. - The New York Times", + "app:id:googleplay": "com.nytimes.android", + "app:name:googleplay": "NYTimes", + "app:url:googleplay": "nytimes://reader/id/100000006583622" + }} + end end diff --git a/test/web/salmon/salmon_test.exs b/test/web/salmon/salmon_test.exs deleted file mode 100644 index e86e76fe9..000000000 --- a/test/web/salmon/salmon_test.exs +++ /dev/null @@ -1,101 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Salmon.SalmonTest do - use Pleroma.DataCase - alias Pleroma.Activity - alias Pleroma.Keys - alias Pleroma.Repo - alias Pleroma.User - alias Pleroma.Web.Federator.Publisher - alias Pleroma.Web.Salmon - import Mock - import Pleroma.Factory - - @magickey "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwQhh-1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB" - - @wrong_magickey "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwQhh-1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAA" - - @magickey_friendica "RSA.AMwa8FUs2fWEjX0xN7yRQgegQffhBpuKNC6fa5VNSVorFjGZhRrlPMn7TQOeihlc9lBz2OsHlIedbYn2uJ7yCs0.AQAB" - - setup_all do - Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) - :ok - end - - test "decodes a salmon" do - {:ok, salmon} = File.read("test/fixtures/salmon.xml") - {:ok, doc} = Salmon.decode_and_validate(@magickey, salmon) - assert Regex.match?(~r/xml/, doc) - end - - test "errors on wrong magic key" do - {:ok, salmon} = File.read("test/fixtures/salmon.xml") - assert Salmon.decode_and_validate(@wrong_magickey, salmon) == :error - end - - test "it encodes a magic key from a public key" do - key = Salmon.decode_key(@magickey) - magic_key = Salmon.encode_key(key) - - assert @magickey == magic_key - end - - test "it decodes a friendica public key" do - _key = Salmon.decode_key(@magickey_friendica) - end - - test "encodes an xml payload with a private key" do - doc = File.read!("test/fixtures/incoming_note_activity.xml") - pem = File.read!("test/fixtures/private_key.pem") - {:ok, private, public} = Keys.keys_from_pem(pem) - - # Let's try a roundtrip. - {:ok, salmon} = Salmon.encode(private, doc) - {:ok, decoded_doc} = Salmon.decode_and_validate(Salmon.encode_key(public), salmon) - - assert doc == decoded_doc - end - - test "it gets a magic key" do - salmon = File.read!("test/fixtures/salmon2.xml") - {:ok, key} = Salmon.fetch_magic_key(salmon) - - assert key == - "RSA.uzg6r1peZU0vXGADWxGJ0PE34WvmhjUmydbX5YYdOiXfODVLwCMi1umGoqUDm-mRu4vNEdFBVJU1CpFA7dKzWgIsqsa501i2XqElmEveXRLvNRWFB6nG03Q5OUY2as8eE54BJm0p20GkMfIJGwP6TSFb-ICp3QjzbatuSPJ6xCE=.AQAB" - end - - test_with_mock "it pushes an activity to remote accounts it's addressed to", - Publisher, - [:passthrough], - [] do - user_data = %{ - info: %{ - salmon: "http://test-example.org/salmon" - }, - local: false - } - - mentioned_user = insert(:user, user_data) - note = insert(:note) - - activity_data = %{ - "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(), - "type" => "Create", - "actor" => note.data["actor"], - "to" => note.data["to"] ++ [mentioned_user.ap_id], - "object" => note.data, - "published_at" => DateTime.utc_now() |> DateTime.to_iso8601(), - "context" => note.data["context"] - } - - {:ok, activity} = Repo.insert(%Activity{data: activity_data, recipients: activity_data["to"]}) - user = User.get_cached_by_ap_id(activity.data["actor"]) - {:ok, user} = User.ensure_keys_present(user) - - Salmon.publish(user, activity) - - assert called(Publisher.enqueue_one(Salmon, %{recipient: mentioned_user})) - end -end diff --git a/test/web/static_fe/static_fe_controller_test.exs b/test/web/static_fe/static_fe_controller_test.exs new file mode 100644 index 000000000..2ce8f9fa3 --- /dev/null +++ b/test/web/static_fe/static_fe_controller_test.exs @@ -0,0 +1,210 @@ +defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do + use Pleroma.Web.ConnCase + alias Pleroma.Activity + alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + clear_config_all([:static_fe, :enabled]) do + Pleroma.Config.put([:static_fe, :enabled], true) + end + + describe "user profile page" do + test "just the profile as HTML", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> put_req_header("accept", "text/html") + |> get("/users/#{user.nickname}") + + assert html_response(conn, 200) =~ user.nickname + end + + test "renders json unless there's an html accept header", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> put_req_header("accept", "application/json") + |> get("/users/#{user.nickname}") + + assert json_response(conn, 200) + end + + test "404 when user not found", %{conn: conn} do + conn = + conn + |> put_req_header("accept", "text/html") + |> get("/users/limpopo") + + assert html_response(conn, 404) =~ "not found" + end + + test "profile does not include private messages", %{conn: conn} do + user = insert(:user) + CommonAPI.post(user, %{"status" => "public"}) + CommonAPI.post(user, %{"status" => "private", "visibility" => "private"}) + + conn = + conn + |> put_req_header("accept", "text/html") + |> get("/users/#{user.nickname}") + + html = html_response(conn, 200) + + assert html =~ ">public<" + refute html =~ ">private<" + end + + test "pagination", %{conn: conn} do + user = insert(:user) + Enum.map(1..30, fn i -> CommonAPI.post(user, %{"status" => "test#{i}"}) end) + + conn = + conn + |> put_req_header("accept", "text/html") + |> get("/users/#{user.nickname}") + + html = html_response(conn, 200) + + assert html =~ ">test30<" + assert html =~ ">test11<" + refute html =~ ">test10<" + refute html =~ ">test1<" + end + + test "pagination, page 2", %{conn: conn} do + user = insert(:user) + activities = Enum.map(1..30, fn i -> CommonAPI.post(user, %{"status" => "test#{i}"}) end) + {:ok, a11} = Enum.at(activities, 11) + + conn = + conn + |> put_req_header("accept", "text/html") + |> get("/users/#{user.nickname}?max_id=#{a11.id}") + + html = html_response(conn, 200) + + assert html =~ ">test1<" + assert html =~ ">test10<" + refute html =~ ">test20<" + refute html =~ ">test29<" + end + end + + describe "notice rendering" do + test "single notice page", %{conn: conn} do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "testing a thing!"}) + + conn = + conn + |> put_req_header("accept", "text/html") + |> get("/notice/#{activity.id}") + + html = html_response(conn, 200) + assert html =~ "<header>" + assert html =~ user.nickname + assert html =~ "testing a thing!" + end + + test "shows the whole thread", %{conn: conn} do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "space: the final frontier"}) + + CommonAPI.post(user, %{ + "status" => "these are the voyages or something", + "in_reply_to_status_id" => activity.id + }) + + conn = + conn + |> put_req_header("accept", "text/html") + |> get("/notice/#{activity.id}") + + html = html_response(conn, 200) + assert html =~ "the final frontier" + assert html =~ "voyages" + end + + test "redirect by AP object ID", %{conn: conn} do + user = insert(:user) + + {:ok, %Activity{data: %{"object" => object_url}}} = + CommonAPI.post(user, %{"status" => "beam me up"}) + + conn = + conn + |> put_req_header("accept", "text/html") + |> get(URI.parse(object_url).path) + + assert html_response(conn, 302) =~ "redirected" + end + + test "redirect by activity ID", %{conn: conn} do + user = insert(:user) + + {:ok, %Activity{data: %{"id" => id}}} = + CommonAPI.post(user, %{"status" => "I'm a doctor, not a devops!"}) + + conn = + conn + |> put_req_header("accept", "text/html") + |> get(URI.parse(id).path) + + assert html_response(conn, 302) =~ "redirected" + end + + test "404 when notice not found", %{conn: conn} do + conn = + conn + |> put_req_header("accept", "text/html") + |> get("/notice/88c9c317") + + assert html_response(conn, 404) =~ "not found" + end + + test "404 for private status", %{conn: conn} do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{"status" => "don't show me!", "visibility" => "private"}) + + conn = + conn + |> put_req_header("accept", "text/html") + |> get("/notice/#{activity.id}") + + assert html_response(conn, 404) =~ "not found" + end + + test "302 for remote cached status", %{conn: conn} do + user = insert(:user) + + message = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "to" => user.follower_address, + "cc" => "https://www.w3.org/ns/activitystreams#Public", + "type" => "Create", + "object" => %{ + "content" => "blah blah blah", + "type" => "Note", + "attributedTo" => user.ap_id, + "inReplyTo" => nil + }, + "actor" => user.ap_id + } + + assert {:ok, activity} = Transmogrifier.handle_incoming(message) + + conn = + conn + |> put_req_header("accept", "text/html") + |> get("/notice/#{activity.id}") + + assert html_response(conn, 302) =~ "redirected" + end + end +end diff --git a/test/web/streamer/ping_test.exs b/test/web/streamer/ping_test.exs index 3d52c00e4..5df6c1cc3 100644 --- a/test/web/streamer/ping_test.exs +++ b/test/web/streamer/ping_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.PingTest do diff --git a/test/web/streamer/state_test.exs b/test/web/streamer/state_test.exs index d1aeac541..a755e75c0 100644 --- a/test/web/streamer/state_test.exs +++ b/test/web/streamer/state_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.StateTest do diff --git a/test/web/streamer/streamer_test.exs b/test/web/streamer/streamer_test.exs index 4818f4c4e..339f99bbf 100644 --- a/test/web/streamer/streamer_test.exs +++ b/test/web/streamer/streamer_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.StreamerTest do @@ -7,6 +7,7 @@ defmodule Pleroma.Web.StreamerTest do import Pleroma.Factory + alias Pleroma.Conversation.Participation alias Pleroma.List alias Pleroma.User alias Pleroma.Web.CommonAPI @@ -14,8 +15,12 @@ defmodule Pleroma.Web.StreamerTest do alias Pleroma.Web.Streamer.StreamerSocket alias Pleroma.Web.Streamer.Worker - @moduletag needs_streamer: true - clear_config_all([:instance, :skip_thread_containment]) + @moduletag needs_streamer: true, capture_log: true + + @streamer_timeout 150 + @streamer_start_wait 10 + + clear_config([:instance, :skip_thread_containment]) describe "user streams" do setup do @@ -27,7 +32,7 @@ defmodule Pleroma.Web.StreamerTest do test "it sends notify to in the 'user' stream", %{user: user, notify: notify} do task = Task.async(fn -> - assert_receive {:text, _}, 4_000 + assert_receive {:text, _}, @streamer_timeout end) Streamer.add_socket( @@ -42,7 +47,7 @@ defmodule Pleroma.Web.StreamerTest do test "it sends notify to in the 'user:notification' stream", %{user: user, notify: notify} do task = Task.async(fn -> - assert_receive {:text, _}, 4_000 + assert_receive {:text, _}, @streamer_timeout end) Streamer.add_socket( @@ -58,12 +63,12 @@ defmodule Pleroma.Web.StreamerTest do user: user } do blocked = insert(:user) - {:ok, user} = User.block(user, blocked) + {:ok, _user_relationship} = User.block(user, blocked) {:ok, activity} = CommonAPI.post(user, %{"status" => ":("}) {:ok, notif, _} = CommonAPI.favorite(activity.id, blocked) - task = Task.async(fn -> refute_receive {:text, _}, 4_000 end) + task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) Streamer.add_socket( "user:notification", @@ -83,7 +88,7 @@ defmodule Pleroma.Web.StreamerTest do {:ok, activity} = CommonAPI.add_mute(user, activity) {:ok, notif, _} = CommonAPI.favorite(activity.id, user2) - task = Task.async(fn -> refute_receive {:text, _}, 4_000 end) + task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) Streamer.add_socket( "user:notification", @@ -103,7 +108,7 @@ defmodule Pleroma.Web.StreamerTest do {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) {:ok, notif, _} = CommonAPI.favorite(activity.id, user2) - task = Task.async(fn -> refute_receive {:text, _}, 4_000 end) + task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) Streamer.add_socket( "user:notification", @@ -118,7 +123,9 @@ defmodule Pleroma.Web.StreamerTest do user: user } do user2 = insert(:user) - task = Task.async(fn -> assert_receive {:text, _}, 4_000 end) + task = Task.async(fn -> assert_receive {:text, _}, @streamer_timeout end) + + Process.sleep(@streamer_start_wait) Streamer.add_socket( "user:notification", @@ -139,7 +146,7 @@ defmodule Pleroma.Web.StreamerTest do task = Task.async(fn -> - assert_receive {:text, _}, 4_000 + assert_receive {:text, _}, @streamer_timeout end) fake_socket = %StreamerSocket{ @@ -166,7 +173,7 @@ defmodule Pleroma.Web.StreamerTest do } |> Jason.encode!() - assert_receive {:text, received_event}, 4_000 + assert_receive {:text, received_event}, @streamer_timeout assert received_event == expected_event end) @@ -190,7 +197,8 @@ defmodule Pleroma.Web.StreamerTest do test "it doesn't send to user if recipients invalid and thread containment is enabled" do Pleroma.Config.put([:instance, :skip_thread_containment], false) author = insert(:user) - user = insert(:user, following: [author.ap_id]) + user = insert(:user) + User.follow(user, author, "accept") activity = insert(:note_activity, @@ -212,7 +220,8 @@ defmodule Pleroma.Web.StreamerTest do test "it sends message if recipients invalid and thread containment is disabled" do Pleroma.Config.put([:instance, :skip_thread_containment], true) author = insert(:user) - user = insert(:user, following: [author.ap_id]) + user = insert(:user) + User.follow(user, author, "accept") activity = insert(:note_activity, @@ -234,7 +243,8 @@ defmodule Pleroma.Web.StreamerTest do test "it sends message if recipients invalid and thread containment is enabled but user's thread containment is disabled" do Pleroma.Config.put([:instance, :skip_thread_containment], false) author = insert(:user) - user = insert(:user, following: [author.ap_id], info: %{skip_thread_containment: true}) + user = insert(:user, skip_thread_containment: true) + User.follow(user, author, "accept") activity = insert(:note_activity, @@ -258,7 +268,7 @@ defmodule Pleroma.Web.StreamerTest do test "it doesn't send messages involving blocked users" do user = insert(:user) blocked_user = insert(:user) - {:ok, user} = User.block(user, blocked_user) + {:ok, _user_relationship} = User.block(user, blocked_user) {:ok, activity} = CommonAPI.post(blocked_user, %{"status" => "Test"}) @@ -300,7 +310,7 @@ defmodule Pleroma.Web.StreamerTest do "public" => [fake_socket] } - {:ok, blocker} = User.block(blocker, blockee) + {:ok, _user_relationship} = User.block(blocker, blockee) {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey! @#{blockee.nickname}"}) @@ -485,9 +495,7 @@ defmodule Pleroma.Web.StreamerTest do {:ok, activity} = CommonAPI.add_mute(user2, activity) - task = Task.async(fn -> refute_receive {:text, _}, 4_000 end) - - Process.sleep(4000) + task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) Streamer.add_socket( "user", @@ -509,7 +517,14 @@ defmodule Pleroma.Web.StreamerTest do task = Task.async(fn -> - assert_receive {:text, _received_event}, 4_000 + assert_receive {:text, received_event}, @streamer_timeout + + assert %{"event" => "conversation", "payload" => received_payload} = + Jason.decode!(received_event) + + assert %{"last_status" => last_status} = Jason.decode!(received_payload) + [participation] = Participation.for_user(user) + assert last_status["pleroma"]["direct_conversation_id"] == participation.id end) Streamer.add_socket( @@ -526,7 +541,7 @@ defmodule Pleroma.Web.StreamerTest do Task.await(task) end - test "it doesn't send conversation update to the 'direct' streamj when the last message in the conversation is deleted" do + test "it doesn't send conversation update to the 'direct' stream when the last message in the conversation is deleted" do user = insert(:user) another_user = insert(:user) @@ -538,13 +553,13 @@ defmodule Pleroma.Web.StreamerTest do task = Task.async(fn -> - assert_receive {:text, received_event}, 4_000 + assert_receive {:text, received_event}, @streamer_timeout assert %{"event" => "delete", "payload" => _} = Jason.decode!(received_event) - refute_receive {:text, _}, 4_000 + refute_receive {:text, _}, @streamer_timeout end) - Process.sleep(1000) + Process.sleep(@streamer_start_wait) Streamer.add_socket( "direct", @@ -575,10 +590,10 @@ defmodule Pleroma.Web.StreamerTest do task = Task.async(fn -> - assert_receive {:text, received_event}, 4_000 + assert_receive {:text, received_event}, @streamer_timeout assert %{"event" => "delete", "payload" => _} = Jason.decode!(received_event) - assert_receive {:text, received_event}, 4_000 + assert_receive {:text, received_event}, @streamer_timeout assert %{"event" => "conversation", "payload" => received_payload} = Jason.decode!(received_event) @@ -587,7 +602,7 @@ defmodule Pleroma.Web.StreamerTest do assert last_status["id"] == to_string(create_activity.id) end) - Process.sleep(1000) + Process.sleep(@streamer_start_wait) Streamer.add_socket( "direct", diff --git a/test/web/twitter_api/password_controller_test.exs b/test/web/twitter_api/password_controller_test.exs index 3a7246ea8..0a24860d3 100644 --- a/test/web/twitter_api/password_controller_test.exs +++ b/test/web/twitter_api/password_controller_test.exs @@ -1,11 +1,12 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.TwitterAPI.PasswordControllerTest do use Pleroma.Web.ConnCase alias Pleroma.PasswordResetToken + alias Pleroma.User alias Pleroma.Web.OAuth.Token import Pleroma.Factory @@ -54,7 +55,27 @@ defmodule Pleroma.Web.TwitterAPI.PasswordControllerTest do user = refresh_record(user) assert Comeonin.Pbkdf2.checkpw("test", user.password_hash) - assert length(Token.get_user_tokens(user)) == 0 + assert Enum.empty?(Token.get_user_tokens(user)) + end + + test "it sets password_reset_pending to false", %{conn: conn} do + user = insert(:user, password_reset_pending: true) + + {:ok, token} = PasswordResetToken.create_token(user) + {:ok, _access_token} = Token.create_token(insert(:oauth_app), user, %{}) + + params = %{ + "password" => "test", + password_confirmation: "test", + token: token.token + } + + conn + |> assign(:user, user) + |> post("/api/pleroma/password_reset", %{data: params}) + |> html_response(:ok) + + assert User.get_by_id(user.id).password_reset_pending == false end end end diff --git a/test/web/twitter_api/remote_follow_controller_test.exs b/test/web/twitter_api/remote_follow_controller_test.exs new file mode 100644 index 000000000..80a42989d --- /dev/null +++ b/test/web/twitter_api/remote_follow_controller_test.exs @@ -0,0 +1,231 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.User + alias Pleroma.Web.CommonAPI + import ExUnit.CaptureLog + import Pleroma.Factory + + setup do + Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + + clear_config([:instance]) + clear_config([:frontend_configurations, :pleroma_fe]) + clear_config([:user, :deny_follow_blocked]) + + describe "GET /ostatus_subscribe - remote_follow/2" do + test "adds status to pleroma instance if the `acct` is a status", %{conn: conn} do + assert conn + |> get( + remote_follow_path(conn, :follow, %{ + acct: "https://mastodon.social/users/emelie/statuses/101849165031453009" + }) + ) + |> redirected_to() =~ "/notice/" + end + + test "show follow account page if the `acct` is a account link", %{conn: conn} do + response = + conn + |> get(remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"})) + |> html_response(200) + + assert response =~ "Log in to follow" + end + + test "show follow page if the `acct` is a account link", %{conn: conn} do + user = insert(:user) + + response = + conn + |> assign(:user, user) + |> get(remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"})) + |> html_response(200) + + assert response =~ "Remote follow" + end + + test "show follow page with error when user cannot fecth by `acct` link", %{conn: conn} do + user = insert(:user) + + assert capture_log(fn -> + response = + conn + |> assign(:user, user) + |> get( + remote_follow_path(conn, :follow, %{ + acct: "https://mastodon.social/users/not_found" + }) + ) + |> html_response(200) + + assert response =~ "Error fetching user" + end) =~ "Object has been deleted" + end + end + + describe "POST /ostatus_subscribe - do_follow/2 with assigned user " do + test "required `follow | write:follows` scope", %{conn: conn} do + user = insert(:user) + user2 = insert(:user) + read_token = insert(:oauth_token, user: user, scopes: ["read"]) + + assert capture_log(fn -> + response = + conn + |> assign(:user, user) + |> assign(:token, read_token) + |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + |> response(200) + + assert response =~ "Error following account" + end) =~ "Insufficient permissions: follow | write:follows." + end + + test "follows user", %{conn: conn} do + user = insert(:user) + user2 = insert(:user) + + conn = + conn + |> assign(:user, user) + |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:follows"])) + |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + + assert redirected_to(conn) == "/users/#{user2.id}" + end + + test "returns error when user is deactivated", %{conn: conn} do + user = insert(:user, deactivated: true) + user2 = insert(:user) + + response = + conn + |> assign(:user, user) + |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + |> response(200) + + assert response =~ "Error following account" + end + + test "returns error when user is blocked", %{conn: conn} do + Pleroma.Config.put([:user, :deny_follow_blocked], true) + user = insert(:user) + user2 = insert(:user) + + {:ok, _user_block} = Pleroma.User.block(user2, user) + + response = + conn + |> assign(:user, user) + |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + |> response(200) + + assert response =~ "Error following account" + end + + test "returns error when followee not found", %{conn: conn} do + user = insert(:user) + + response = + conn + |> assign(:user, user) + |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => "jimm"}}) + |> response(200) + + assert response =~ "Error following account" + end + + test "returns success result when user already in followers", %{conn: conn} do + user = insert(:user) + user2 = insert(:user) + {:ok, _, _, _} = CommonAPI.follow(user, user2) + + conn = + conn + |> assign(:user, refresh_record(user)) + |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:follows"])) + |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + + assert redirected_to(conn) == "/users/#{user2.id}" + end + end + + describe "POST /ostatus_subscribe - follow/2 without assigned user " do + test "follows", %{conn: conn} do + user = insert(:user) + user2 = insert(:user) + + conn = + conn + |> post(remote_follow_path(conn, :do_follow), %{ + "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} + }) + + assert redirected_to(conn) == "/users/#{user2.id}" + assert user2.follower_address in User.following(user) + end + + test "returns error when followee not found", %{conn: conn} do + user = insert(:user) + + response = + conn + |> post(remote_follow_path(conn, :do_follow), %{ + "authorization" => %{"name" => user.nickname, "password" => "test", "id" => "jimm"} + }) + |> response(200) + + assert response =~ "Error following account" + end + + test "returns error when login invalid", %{conn: conn} do + user = insert(:user) + + response = + conn + |> post(remote_follow_path(conn, :do_follow), %{ + "authorization" => %{"name" => "jimm", "password" => "test", "id" => user.id} + }) + |> response(200) + + assert response =~ "Wrong username or password" + end + + test "returns error when password invalid", %{conn: conn} do + user = insert(:user) + user2 = insert(:user) + + response = + conn + |> post(remote_follow_path(conn, :do_follow), %{ + "authorization" => %{"name" => user.nickname, "password" => "42", "id" => user2.id} + }) + |> response(200) + + assert response =~ "Wrong username or password" + end + + test "returns error when user is blocked", %{conn: conn} do + Pleroma.Config.put([:user, :deny_follow_blocked], true) + user = insert(:user) + user2 = insert(:user) + {:ok, _user_block} = Pleroma.User.block(user2, user) + + response = + conn + |> post(remote_follow_path(conn, :do_follow), %{ + "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} + }) + |> response(200) + + assert response =~ "Error following account" + end + end +end diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/web/twitter_api/twitter_api_controller_test.exs new file mode 100644 index 000000000..ab0a2c3df --- /dev/null +++ b/test/web/twitter_api/twitter_api_controller_test.exs @@ -0,0 +1,142 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.TwitterAPI.ControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Builders.ActivityBuilder + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Web.OAuth.Token + + import Pleroma.Factory + + describe "POST /api/qvitter/statuses/notifications/read" do + test "without valid credentials", %{conn: conn} do + conn = post(conn, "/api/qvitter/statuses/notifications/read", %{"latest_id" => 1_234_567}) + assert json_response(conn, 403) == %{"error" => "Invalid credentials."} + end + + test "with credentials, without any params" do + %{user: current_user, conn: conn} = + oauth_access(["read:notifications", "write:notifications"]) + + conn = + conn + |> assign(:user, current_user) + |> post("/api/qvitter/statuses/notifications/read") + + assert json_response(conn, 400) == %{ + "error" => "You need to specify latest_id", + "request" => "/api/qvitter/statuses/notifications/read" + } + end + + test "with credentials, with params" do + %{user: current_user, conn: conn} = + oauth_access(["read:notifications", "write:notifications"]) + + other_user = insert(:user) + + {:ok, _activity} = + ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: other_user}) + + response_conn = + conn + |> assign(:user, current_user) + |> get("/api/v1/notifications") + + [notification] = response = json_response(response_conn, 200) + + assert length(response) == 1 + + assert notification["pleroma"]["is_seen"] == false + + response_conn = + conn + |> assign(:user, current_user) + |> post("/api/qvitter/statuses/notifications/read", %{"latest_id" => notification["id"]}) + + [notification] = response = json_response(response_conn, 200) + + assert length(response) == 1 + + assert notification["pleroma"]["is_seen"] == true + end + end + + describe "GET /api/account/confirm_email/:id/:token" do + setup do + {:ok, user} = + insert(:user) + |> User.confirmation_changeset(need_confirmation: true) + |> Repo.update() + + assert user.confirmation_pending + + [user: user] + end + + test "it redirects to root url", %{conn: conn, user: user} do + conn = get(conn, "/api/account/confirm_email/#{user.id}/#{user.confirmation_token}") + + assert 302 == conn.status + end + + test "it confirms the user account", %{conn: conn, user: user} do + get(conn, "/api/account/confirm_email/#{user.id}/#{user.confirmation_token}") + + user = User.get_cached_by_id(user.id) + + refute user.confirmation_pending + refute user.confirmation_token + end + + test "it returns 500 if user cannot be found by id", %{conn: conn, user: user} do + conn = get(conn, "/api/account/confirm_email/0/#{user.confirmation_token}") + + assert 500 == conn.status + end + + test "it returns 500 if token is invalid", %{conn: conn, user: user} do + conn = get(conn, "/api/account/confirm_email/#{user.id}/wrong_token") + + assert 500 == conn.status + end + end + + describe "GET /api/oauth_tokens" do + setup do + token = insert(:oauth_token) |> Repo.preload(:user) + + %{token: token} + end + + test "renders list", %{token: token} do + response = + build_conn() + |> assign(:user, token.user) + |> get("/api/oauth_tokens") + + keys = + json_response(response, 200) + |> hd() + |> Map.keys() + + assert keys -- ["id", "app_name", "valid_until"] == [] + end + + test "revoke token", %{token: token} do + response = + build_conn() + |> assign(:user, token.user) + |> delete("/api/oauth_tokens/#{token.id}") + + tokens = Token.get_user_tokens(token.user) + + assert tokens == [] + assert response.status == 201 + end + end +end diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs index c5b18234e..14eed5f27 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -1,10 +1,11 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do use Pleroma.DataCase alias Pleroma.Repo + alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.UserInviteToken alias Pleroma.Web.MastodonAPI.AccountView @@ -28,8 +29,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do fetched_user = User.get_cached_by_nickname("lain") - assert AccountView.render("account.json", %{user: user}) == - AccountView.render("account.json", %{user: fetched_user}) + assert AccountView.render("show.json", %{user: user}) == + AccountView.render("show.json", %{user: fetched_user}) end test "it registers a new user with empty string in bio and returns the user." do @@ -46,8 +47,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do fetched_user = User.get_cached_by_nickname("lain") - assert AccountView.render("account.json", %{user: user}) == - AccountView.render("account.json", %{user: fetched_user}) + assert AccountView.render("show.json", %{user: user}) == + AccountView.render("show.json", %{user: fetched_user}) end test "it sends confirmation email if :account_activation_required is specified in instance config" do @@ -68,8 +69,9 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do } {:ok, user} = TwitterAPI.register_user(data) + ObanHelpers.perform_all() - assert user.info.confirmation_pending + assert user.confirmation_pending email = Pleroma.Emails.UserEmail.account_confirmation_email(user) @@ -107,21 +109,16 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do {:ok, user2} = TwitterAPI.register_user(data2) expected_text = - "<span class='h-card'><a data-user='#{user1.id}' class='u-url mention' href='#{user1.ap_id}'>@<span>john</span></a></span> test" + ~s(<span class="h-card"><a data-user="#{user1.id}" class="u-url mention" href="#{ + user1.ap_id + }" rel="ugc">@<span>john</span></a></span> test) assert user2.bio == expected_text end describe "register with one time token" do - setup do - setting = Pleroma.Config.get([:instance, :registrations_open]) - - if setting do - Pleroma.Config.put([:instance, :registrations_open], false) - on_exit(fn -> Pleroma.Config.put([:instance, :registrations_open], setting) end) - end - - :ok + clear_config([:instance, :registrations_open]) do + Pleroma.Config.put([:instance, :registrations_open], false) end test "returns user on success" do @@ -144,8 +141,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do assert invite.used == true - assert AccountView.render("account.json", %{user: user}) == - AccountView.render("account.json", %{user: fetched_user}) + assert AccountView.render("show.json", %{user: user}) == + AccountView.render("show.json", %{user: fetched_user}) end test "returns error on invalid token" do @@ -187,14 +184,11 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do end describe "registers with date limited token" do - setup do - setting = Pleroma.Config.get([:instance, :registrations_open]) - - if setting do - Pleroma.Config.put([:instance, :registrations_open], false) - on_exit(fn -> Pleroma.Config.put([:instance, :registrations_open], setting) end) - end + clear_config([:instance, :registrations_open]) do + Pleroma.Config.put([:instance, :registrations_open], false) + end + setup do data = %{ "nickname" => "vinny", "email" => "pasta@pizza.vs", @@ -209,8 +203,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do {:ok, user} = TwitterAPI.register_user(data) fetched_user = User.get_cached_by_nickname("vinny") - assert AccountView.render("account.json", %{user: user}) == - AccountView.render("account.json", %{user: fetched_user}) + assert AccountView.render("show.json", %{user: user}) == + AccountView.render("show.json", %{user: fetched_user}) end {:ok, data: data, check_fn: check_fn} @@ -252,15 +246,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do end describe "registers with reusable token" do - setup do - setting = Pleroma.Config.get([:instance, :registrations_open]) - - if setting do - Pleroma.Config.put([:instance, :registrations_open], false) - on_exit(fn -> Pleroma.Config.put([:instance, :registrations_open], setting) end) - end - - :ok + clear_config([:instance, :registrations_open]) do + Pleroma.Config.put([:instance, :registrations_open], false) end test "returns user on success, after him registration fails" do @@ -284,8 +271,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do assert invite.used == true - assert AccountView.render("account.json", %{user: user}) == - AccountView.render("account.json", %{user: fetched_user}) + assert AccountView.render("show.json", %{user: user}) == + AccountView.render("show.json", %{user: fetched_user}) data = %{ "nickname" => "GrimReaper", @@ -305,15 +292,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do end describe "registers with reusable date limited token" do - setup do - setting = Pleroma.Config.get([:instance, :registrations_open]) - - if setting do - Pleroma.Config.put([:instance, :registrations_open], false) - on_exit(fn -> Pleroma.Config.put([:instance, :registrations_open], setting) end) - end - - :ok + clear_config([:instance, :registrations_open]) do + Pleroma.Config.put([:instance, :registrations_open], false) end test "returns user on success" do @@ -335,8 +315,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do refute invite.used - assert AccountView.render("account.json", %{user: user}) == - AccountView.render("account.json", %{user: fetched_user}) + assert AccountView.render("show.json", %{user: user}) == + AccountView.render("show.json", %{user: fetched_user}) end test "error after max uses" do @@ -359,8 +339,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do invite = Repo.get_by(UserInviteToken, token: invite.token) assert invite.used == true - assert AccountView.render("account.json", %{user: user}) == - AccountView.render("account.json", %{user: fetched_user}) + assert AccountView.render("show.json", %{user: user}) == + AccountView.render("show.json", %{user: fetched_user}) data = %{ "nickname" => "GrimReaper", diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs index a3c6145c0..d464ce215 100644 --- a/test/web/twitter_api/util_controller_test.exs +++ b/test/web/twitter_api/util_controller_test.exs @@ -1,13 +1,14 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do use Pleroma.Web.ConnCase + use Oban.Testing, repo: Pleroma.Repo - alias Pleroma.Repo + alias Pleroma.Tests.ObanHelpers alias Pleroma.User - alias Pleroma.Web.CommonAPI + import Pleroma.Factory import Mock @@ -18,58 +19,53 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do clear_config([:instance]) clear_config([:frontend_configurations, :pleroma_fe]) - clear_config([:user, :deny_follow_blocked]) describe "POST /api/pleroma/follow_import" do + setup do: oauth_access(["follow"]) + test "it returns HTTP 200", %{conn: conn} do - user1 = insert(:user) user2 = insert(:user) response = conn - |> assign(:user, user1) |> post("/api/pleroma/follow_import", %{"list" => "#{user2.ap_id}"}) |> json_response(:ok) assert response == "job started" end - test "it imports follow lists from file", %{conn: conn} do - user1 = insert(:user) + test "it imports follow lists from file", %{user: user1, conn: conn} do user2 = insert(:user) with_mocks([ {File, [], read!: fn "follow_list.txt" -> "Account address,Show boosts\n#{user2.ap_id},true" - end}, - {PleromaJobQueue, [:passthrough], []} + end} ]) do response = conn - |> assign(:user, user1) |> post("/api/pleroma/follow_import", %{"list" => %Plug.Upload{path: "follow_list.txt"}}) |> json_response(:ok) - assert called( - PleromaJobQueue.enqueue( - :background, - User, - [:follow_import, user1, [user2.ap_id]] - ) - ) - assert response == "job started" + + assert ObanHelpers.member?( + %{ + "op" => "follow_import", + "follower_id" => user1.id, + "followed_identifiers" => [user2.ap_id] + }, + all_enqueued(worker: Pleroma.Workers.BackgroundWorker) + ) end end test "it imports new-style mastodon follow lists", %{conn: conn} do - user1 = insert(:user) user2 = insert(:user) response = conn - |> assign(:user, user1) |> post("/api/pleroma/follow_import", %{ "list" => "Account address,Show boosts\n#{user2.ap_id},true" }) @@ -78,19 +74,21 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do assert response == "job started" end - test "requires 'follow' permission", %{conn: conn} do + test "requires 'follow' or 'write:follows' permissions" do token1 = insert(:oauth_token, scopes: ["read", "write"]) token2 = insert(:oauth_token, scopes: ["follow"]) + token3 = insert(:oauth_token, scopes: ["something"]) another_user = insert(:user) - for token <- [token1, token2] do + for token <- [token1, token2, token3] do conn = - conn + build_conn() |> put_req_header("authorization", "Bearer #{token.token}") |> post("/api/pleroma/follow_import", %{"list" => "#{another_user.ap_id}"}) - if token == token1 do - assert %{"error" => "Insufficient permissions: follow."} == json_response(conn, 403) + if token == token3 do + assert %{"error" => "Insufficient permissions: follow | write:follows."} == + json_response(conn, 403) else assert json_response(conn, 200) end @@ -99,67 +97,82 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do end describe "POST /api/pleroma/blocks_import" do + # Note: "follow" or "write:blocks" permission is required + setup do: oauth_access(["write:blocks"]) + test "it returns HTTP 200", %{conn: conn} do - user1 = insert(:user) user2 = insert(:user) response = conn - |> assign(:user, user1) |> post("/api/pleroma/blocks_import", %{"list" => "#{user2.ap_id}"}) |> json_response(:ok) assert response == "job started" end - test "it imports blocks users from file", %{conn: conn} do - user1 = insert(:user) + test "it imports blocks users from file", %{user: user1, conn: conn} do user2 = insert(:user) user3 = insert(:user) with_mocks([ - {File, [], read!: fn "blocks_list.txt" -> "#{user2.ap_id} #{user3.ap_id}" end}, - {PleromaJobQueue, [:passthrough], []} + {File, [], read!: fn "blocks_list.txt" -> "#{user2.ap_id} #{user3.ap_id}" end} ]) do response = conn - |> assign(:user, user1) |> post("/api/pleroma/blocks_import", %{"list" => %Plug.Upload{path: "blocks_list.txt"}}) |> json_response(:ok) - assert called( - PleromaJobQueue.enqueue( - :background, - User, - [:blocks_import, user1, [user2.ap_id, user3.ap_id]] - ) - ) - assert response == "job started" + + assert ObanHelpers.member?( + %{ + "op" => "blocks_import", + "blocker_id" => user1.id, + "blocked_identifiers" => [user2.ap_id, user3.ap_id] + }, + all_enqueued(worker: Pleroma.Workers.BackgroundWorker) + ) end end end describe "PUT /api/pleroma/notification_settings" do - test "it updates notification settings", %{conn: conn} do - user = insert(:user) + setup do: oauth_access(["write:accounts"]) + test "it updates notification settings", %{user: user, conn: conn} do conn - |> assign(:user, user) |> put("/api/pleroma/notification_settings", %{ "followers" => false, "bar" => 1 }) |> json_response(:ok) - user = Repo.get(User, user.id) + user = refresh_record(user) - assert %{ - "followers" => false, - "follows" => true, - "non_follows" => true, - "non_followers" => true - } == user.info.notification_settings + assert %Pleroma.User.NotificationSetting{ + followers: false, + follows: true, + non_follows: true, + non_followers: true, + privacy_option: false + } == user.notification_settings + end + + test "it updates notification privacy option", %{user: user, conn: conn} do + conn + |> put("/api/pleroma/notification_settings", %{"privacy_option" => "1"}) + |> json_response(:ok) + + user = refresh_record(user) + + assert %Pleroma.User.NotificationSetting{ + followers: true, + follows: true, + non_follows: true, + non_followers: true, + privacy_option: true + } == user.notification_settings end end @@ -303,194 +316,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do end end - describe "GET /ostatus_subscribe - remote_follow/2" do - test "adds status to pleroma instance if the `acct` is a status", %{conn: conn} do - conn = - get( - conn, - "/ostatus_subscribe?acct=https://mastodon.social/users/emelie/statuses/101849165031453009" - ) - - assert redirected_to(conn) =~ "/notice/" - end - - test "show follow account page if the `acct` is a account link", %{conn: conn} do - response = - get( - conn, - "/ostatus_subscribe?acct=https://mastodon.social/users/emelie" - ) - - assert html_response(response, 200) =~ "Log in to follow" - end - - test "show follow page if the `acct` is a account link", %{conn: conn} do - user = insert(:user) - - response = - conn - |> assign(:user, user) - |> get("/ostatus_subscribe?acct=https://mastodon.social/users/emelie") - - assert html_response(response, 200) =~ "Remote follow" - end - - test "show follow page with error when user cannot fecth by `acct` link", %{conn: conn} do - user = insert(:user) - - response = - conn - |> assign(:user, user) - |> get("/ostatus_subscribe?acct=https://mastodon.social/users/not_found") - - assert html_response(response, 200) =~ "Error fetching user" - end - end - - describe "POST /ostatus_subscribe - do_remote_follow/2 with assigned user " do - test "follows user", %{conn: conn} do - user = insert(:user) - user2 = insert(:user) - - response = - conn - |> assign(:user, user) - |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) - |> response(200) - - assert response =~ "Account followed!" - assert user2.follower_address in refresh_record(user).following - end - - test "returns error when user is deactivated", %{conn: conn} do - user = insert(:user, info: %{deactivated: true}) - user2 = insert(:user) - - response = - conn - |> assign(:user, user) - |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) - |> response(200) - - assert response =~ "Error following account" - end - - test "returns error when user is blocked", %{conn: conn} do - Pleroma.Config.put([:user, :deny_follow_blocked], true) - user = insert(:user) - user2 = insert(:user) - - {:ok, _user} = Pleroma.User.block(user2, user) - - response = - conn - |> assign(:user, user) - |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) - |> response(200) - - assert response =~ "Error following account" - end - - test "returns error when followee not found", %{conn: conn} do - user = insert(:user) - - response = - conn - |> assign(:user, user) - |> post("/ostatus_subscribe", %{"user" => %{"id" => "jimm"}}) - |> response(200) - - assert response =~ "Error following account" - end - - test "returns success result when user already in followers", %{conn: conn} do - user = insert(:user) - user2 = insert(:user) - {:ok, _, _, _} = CommonAPI.follow(user, user2) - - response = - conn - |> assign(:user, refresh_record(user)) - |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) - |> response(200) - - assert response =~ "Account followed!" - end - end - - describe "POST /ostatus_subscribe - do_remote_follow/2 without assigned user " do - test "follows", %{conn: conn} do - user = insert(:user) - user2 = insert(:user) - - response = - conn - |> post("/ostatus_subscribe", %{ - "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} - }) - |> response(200) - - assert response =~ "Account followed!" - assert user2.follower_address in refresh_record(user).following - end - - test "returns error when followee not found", %{conn: conn} do - user = insert(:user) - - response = - conn - |> post("/ostatus_subscribe", %{ - "authorization" => %{"name" => user.nickname, "password" => "test", "id" => "jimm"} - }) - |> response(200) - - assert response =~ "Error following account" - end - - test "returns error when login invalid", %{conn: conn} do - user = insert(:user) - - response = - conn - |> post("/ostatus_subscribe", %{ - "authorization" => %{"name" => "jimm", "password" => "test", "id" => user.id} - }) - |> response(200) - - assert response =~ "Wrong username or password" - end - - test "returns error when password invalid", %{conn: conn} do - user = insert(:user) - user2 = insert(:user) - - response = - conn - |> post("/ostatus_subscribe", %{ - "authorization" => %{"name" => user.nickname, "password" => "42", "id" => user2.id} - }) - |> response(200) - - assert response =~ "Wrong username or password" - end - - test "returns error when user is blocked", %{conn: conn} do - Pleroma.Config.put([:user, :deny_follow_blocked], true) - user = insert(:user) - user2 = insert(:user) - {:ok, _user} = Pleroma.User.block(user2, user) - - response = - conn - |> post("/ostatus_subscribe", %{ - "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} - }) - |> response(200) - - assert response =~ "Error following account" - end - end - describe "GET /api/pleroma/healthcheck" do clear_config([:instance, :healthcheck]) @@ -525,7 +350,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do end end - test "returns 503 when healthcheck enabled and health is false", %{conn: conn} do + test "returns 503 when healthcheck enabled and health is false", %{conn: conn} do Pleroma.Config.put([:instance, :healthcheck], true) with_mock Pleroma.Healthcheck, @@ -547,35 +372,34 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do end describe "POST /api/pleroma/disable_account" do - test "it returns HTTP 200", %{conn: conn} do - user = insert(:user) + setup do: oauth_access(["write:accounts"]) + test "with valid permissions and password, it disables the account", %{conn: conn, user: user} do response = conn - |> assign(:user, user) |> post("/api/pleroma/disable_account", %{"password" => "test"}) |> json_response(:ok) assert response == %{"status" => "success"} + ObanHelpers.perform_all() user = User.get_cached_by_id(user.id) - assert user.info.deactivated == true + assert user.deactivated == true end - test "it returns returns when password invalid", %{conn: conn} do + test "with valid permissions and invalid password, it returns an error", %{conn: conn} do user = insert(:user) response = conn - |> assign(:user, user) |> post("/api/pleroma/disable_account", %{"password" => "test1"}) |> json_response(:ok) assert response == %{"error" => "Invalid password."} user = User.get_cached_by_id(user.id) - refute user.info.deactivated + refute user.deactivated end end @@ -638,7 +462,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do "https://social.heldscal.la/main/ostatussub?profile=#{user.ap_id}" end - test "it renders form with error when use not found", %{conn: conn} do + test "it renders form with error when user not found", %{conn: conn} do user2 = insert(:user, ap_id: "shp@social.heldscal.la") response = @@ -663,29 +487,21 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do end end - defp with_credentials(conn, username, password) do - header_content = "Basic " <> Base.encode64("#{username}:#{password}") - put_req_header(conn, "authorization", header_content) - end - - defp valid_user(_context) do - user = insert(:user) - [user: user] - end - describe "POST /api/pleroma/change_email" do - setup [:valid_user] + setup do: oauth_access(["write:accounts"]) + + test "without permissions", %{conn: conn} do + conn = + conn + |> assign(:token, nil) + |> post("/api/pleroma/change_email") - test "without credentials", %{conn: conn} do - conn = post(conn, "/api/pleroma/change_email") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} + assert json_response(conn, 403) == %{"error" => "Insufficient permissions: write:accounts."} end - test "with credentials and invalid password", %{conn: conn, user: current_user} do + test "with proper permissions and invalid password", %{conn: conn} do conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/change_email", %{ + post(conn, "/api/pleroma/change_email", %{ "password" => "hi", "email" => "test@test.com" }) @@ -693,14 +509,11 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do assert json_response(conn, 200) == %{"error" => "Invalid password."} end - test "with credentials, valid password and invalid email", %{ - conn: conn, - user: current_user + test "with proper permissions, valid password and invalid email", %{ + conn: conn } do conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/change_email", %{ + post(conn, "/api/pleroma/change_email", %{ "password" => "test", "email" => "foobar" }) @@ -708,28 +521,22 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do assert json_response(conn, 200) == %{"error" => "Email has invalid format."} end - test "with credentials, valid password and no email", %{ - conn: conn, - user: current_user + test "with proper permissions, valid password and no email", %{ + conn: conn } do conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/change_email", %{ + post(conn, "/api/pleroma/change_email", %{ "password" => "test" }) assert json_response(conn, 200) == %{"error" => "Email can't be blank."} end - test "with credentials, valid password and blank email", %{ - conn: conn, - user: current_user + test "with proper permissions, valid password and blank email", %{ + conn: conn } do conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/change_email", %{ + post(conn, "/api/pleroma/change_email", %{ "password" => "test", "email" => "" }) @@ -737,16 +544,13 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do assert json_response(conn, 200) == %{"error" => "Email can't be blank."} end - test "with credentials, valid password and non unique email", %{ - conn: conn, - user: current_user + test "with proper permissions, valid password and non unique email", %{ + conn: conn } do user = insert(:user) conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/change_email", %{ + post(conn, "/api/pleroma/change_email", %{ "password" => "test", "email" => user.email }) @@ -754,14 +558,11 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do assert json_response(conn, 200) == %{"error" => "Email has already been taken."} end - test "with credentials, valid password and valid email", %{ - conn: conn, - user: current_user + test "with proper permissions, valid password and valid email", %{ + conn: conn } do conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/change_email", %{ + post(conn, "/api/pleroma/change_email", %{ "password" => "test", "email" => "cofe@foobar.com" }) @@ -769,4 +570,103 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do assert json_response(conn, 200) == %{"status" => "success"} end end + + describe "POST /api/pleroma/change_password" do + setup do: oauth_access(["write:accounts"]) + + test "without permissions", %{conn: conn} do + conn = + conn + |> assign(:token, nil) + |> post("/api/pleroma/change_password") + + assert json_response(conn, 403) == %{"error" => "Insufficient permissions: write:accounts."} + end + + test "with proper permissions and invalid password", %{conn: conn} do + conn = + post(conn, "/api/pleroma/change_password", %{ + "password" => "hi", + "new_password" => "newpass", + "new_password_confirmation" => "newpass" + }) + + assert json_response(conn, 200) == %{"error" => "Invalid password."} + end + + test "with proper permissions, valid password and new password and confirmation not matching", + %{ + conn: conn + } do + conn = + post(conn, "/api/pleroma/change_password", %{ + "password" => "test", + "new_password" => "newpass", + "new_password_confirmation" => "notnewpass" + }) + + assert json_response(conn, 200) == %{ + "error" => "New password does not match confirmation." + } + end + + test "with proper permissions, valid password and invalid new password", %{ + conn: conn + } do + conn = + post(conn, "/api/pleroma/change_password", %{ + "password" => "test", + "new_password" => "", + "new_password_confirmation" => "" + }) + + assert json_response(conn, 200) == %{ + "error" => "New password can't be blank." + } + end + + test "with proper permissions, valid password and matching new password and confirmation", %{ + conn: conn, + user: user + } do + conn = + post(conn, "/api/pleroma/change_password", %{ + "password" => "test", + "new_password" => "newpass", + "new_password_confirmation" => "newpass" + }) + + assert json_response(conn, 200) == %{"status" => "success"} + fetched_user = User.get_cached_by_id(user.id) + assert Comeonin.Pbkdf2.checkpw("newpass", fetched_user.password_hash) == true + end + end + + describe "POST /api/pleroma/delete_account" do + setup do: oauth_access(["write:accounts"]) + + test "without permissions", %{conn: conn} do + conn = + conn + |> assign(:token, nil) + |> post("/api/pleroma/delete_account") + + assert json_response(conn, 403) == + %{"error" => "Insufficient permissions: write:accounts."} + end + + test "with proper permissions and wrong or missing password", %{conn: conn} do + for params <- [%{"password" => "hi"}, %{}] do + ret_conn = post(conn, "/api/pleroma/delete_account", params) + + assert json_response(ret_conn, 200) == %{"error" => "Invalid password."} + end + end + + test "with proper permissions and valid password", %{conn: conn} do + conn = post(conn, "/api/pleroma/delete_account", %{"password" => "test"}) + + assert json_response(conn, 200) == %{"status" => "success"} + end + end end diff --git a/test/web/uploader_controller_test.exs b/test/web/uploader_controller_test.exs index 70028df1c..21e518236 100644 --- a/test/web/uploader_controller_test.exs +++ b/test/web/uploader_controller_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.UploaderControllerTest do diff --git a/test/web/views/error_view_test.exs b/test/web/views/error_view_test.exs index 3857d585f..8dbbd18b4 100644 --- a/test/web/views/error_view_test.exs +++ b/test/web/views/error_view_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ErrorViewTest do diff --git a/test/web/web_finger/web_finger_controller_test.exs b/test/web/web_finger/web_finger_controller_test.exs index e23086b2a..b65bf5904 100644 --- a/test/web/web_finger/web_finger_controller_test.exs +++ b/test/web/web_finger/web_finger_controller_test.exs @@ -1,10 +1,11 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do use Pleroma.Web.ConnCase + import ExUnit.CaptureLog import Pleroma.Factory import Tesla.Mock @@ -75,11 +76,13 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do test "Sends a 404 when invalid format" do user = insert(:user) - assert_raise Phoenix.NotAcceptableError, fn -> - build_conn() - |> put_req_header("accept", "text/html") - |> get("/.well-known/webfinger?resource=acct:#{user.nickname}@localhost") - end + assert capture_log(fn -> + assert_raise Phoenix.NotAcceptableError, fn -> + build_conn() + |> put_req_header("accept", "text/html") + |> get("/.well-known/webfinger?resource=acct:#{user.nickname}@localhost") + end + end) =~ "no supported media type in accept header" end test "Sends a 400 when resource param is missing" do diff --git a/test/web/web_finger/web_finger_test.exs b/test/web/web_finger/web_finger_test.exs index 8fdb9adea..4b4282727 100644 --- a/test/web/web_finger/web_finger_test.exs +++ b/test/web/web_finger/web_finger_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.WebFingerTest do @@ -45,19 +45,6 @@ defmodule Pleroma.Web.WebFingerTest do assert {:error, %Jason.DecodeError{}} = WebFinger.finger(user) end - test "returns the info for an OStatus user" do - user = "shp@social.heldscal.la" - - {:ok, data} = WebFinger.finger(user) - - assert data["magic_key"] == - "RSA.wQ3i9UA0qmAxZ0WTIp4a-waZn_17Ez1pEEmqmqoooRsG1_BvpmOvLN0G2tEcWWxl2KOtdQMCiPptmQObeZeuj48mdsDZ4ArQinexY2hCCTcbV8Xpswpkb8K05RcKipdg07pnI7tAgQ0VWSZDImncL6YUGlG5YN8b5TjGOwk2VG8=.AQAB" - - assert data["topic"] == "https://social.heldscal.la/api/statuses/user_timeline/29191.atom" - assert data["subject"] == "acct:shp@social.heldscal.la" - assert data["salmon"] == "https://social.heldscal.la/main/salmon/user/29191" - end - test "returns the ActivityPub actor URI for an ActivityPub user" do user = "framasoft@framatube.org" @@ -72,20 +59,6 @@ defmodule Pleroma.Web.WebFingerTest do assert data["ap_id"] == "https://gerzilla.de/channel/kaniini" end - test "returns the correctly for json ostatus users" do - user = "winterdienst@gnusocial.de" - - {:ok, data} = WebFinger.finger(user) - - assert data["magic_key"] == - "RSA.qfYaxztz7ZELrE4v5WpJrPM99SKI3iv9Y3Tw6nfLGk-4CRljNYqV8IYX2FXjeucC_DKhPNnlF6fXyASpcSmA_qupX9WC66eVhFhZ5OuyBOeLvJ1C4x7Hi7Di8MNBxY3VdQuQR0tTaS_YAZCwASKp7H6XEid3EJpGt0EQZoNzRd8=.AQAB" - - assert data["topic"] == "https://gnusocial.de/api/statuses/user_timeline/249296.atom" - assert data["subject"] == "acct:winterdienst@gnusocial.de" - assert data["salmon"] == "https://gnusocial.de/main/salmon/user/249296" - assert data["subscribe_address"] == "https://gnusocial.de/main/ostatussub?profile={uri}" - end - test "it work for AP-only user" do user = "kpherox@mstdn.jp" diff --git a/test/web/websub/websub_controller_test.exs b/test/web/websub/websub_controller_test.exs deleted file mode 100644 index 59cacbe68..000000000 --- a/test/web/websub/websub_controller_test.exs +++ /dev/null @@ -1,86 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Websub.WebsubControllerTest do - use Pleroma.Web.ConnCase - import Pleroma.Factory - alias Pleroma.Repo - alias Pleroma.Web.Websub - alias Pleroma.Web.Websub.WebsubClientSubscription - - clear_config_all([:instance, :federating]) do - Pleroma.Config.put([:instance, :federating], true) - end - - test "websub subscription request", %{conn: conn} do - user = insert(:user) - - path = Pleroma.Web.OStatus.pubsub_path(user) - - data = %{ - "hub.callback": "http://example.org/sub", - "hub.mode": "subscribe", - "hub.topic": Pleroma.Web.OStatus.feed_path(user), - "hub.secret": "a random secret", - "hub.lease_seconds": "100" - } - - conn = - conn - |> post(path, data) - - assert response(conn, 202) == "Accepted" - end - - test "websub subscription confirmation", %{conn: conn} do - websub = insert(:websub_client_subscription) - - params = %{ - "hub.mode" => "subscribe", - "hub.topic" => websub.topic, - "hub.challenge" => "some challenge", - "hub.lease_seconds" => "100" - } - - conn = - conn - |> get("/push/subscriptions/#{websub.id}", params) - - websub = Repo.get(WebsubClientSubscription, websub.id) - - assert response(conn, 200) == "some challenge" - assert websub.state == "accepted" - assert_in_delta NaiveDateTime.diff(websub.valid_until, NaiveDateTime.utc_now()), 100, 5 - end - - describe "websub_incoming" do - test "accepts incoming feed updates", %{conn: conn} do - websub = insert(:websub_client_subscription) - doc = "some stuff" - signature = Websub.sign(websub.secret, doc) - - conn = - conn - |> put_req_header("x-hub-signature", "sha1=" <> signature) - |> put_req_header("content-type", "application/atom+xml") - |> post("/push/subscriptions/#{websub.id}", doc) - - assert response(conn, 200) == "OK" - end - - test "rejects incoming feed updates with the wrong signature", %{conn: conn} do - websub = insert(:websub_client_subscription) - doc = "some stuff" - signature = Websub.sign("wrong secret", doc) - - conn = - conn - |> put_req_header("x-hub-signature", "sha1=" <> signature) - |> put_req_header("content-type", "application/atom+xml") - |> post("/push/subscriptions/#{websub.id}", doc) - - assert response(conn, 500) == "Error" - end - end -end diff --git a/test/web/websub/websub_test.exs b/test/web/websub/websub_test.exs deleted file mode 100644 index 74386d7db..000000000 --- a/test/web/websub/websub_test.exs +++ /dev/null @@ -1,232 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.WebsubTest do - use Pleroma.DataCase - - alias Pleroma.Web.Router.Helpers - alias Pleroma.Web.Websub - alias Pleroma.Web.Websub.WebsubClientSubscription - alias Pleroma.Web.Websub.WebsubServerSubscription - - import Pleroma.Factory - import Tesla.Mock - - setup do - mock(fn env -> apply(HttpRequestMock, :request, [env]) end) - :ok - end - - test "a verification of a request that is accepted" do - sub = insert(:websub_subscription) - topic = sub.topic - - getter = fn _path, _headers, options -> - %{ - "hub.challenge": challenge, - "hub.lease_seconds": seconds, - "hub.topic": ^topic, - "hub.mode": "subscribe" - } = Keyword.get(options, :params) - - assert String.to_integer(seconds) > 0 - - {:ok, - %Tesla.Env{ - status: 200, - body: challenge - }} - end - - {:ok, sub} = Websub.verify(sub, getter) - assert sub.state == "active" - end - - test "a verification of a request that doesn't return 200" do - sub = insert(:websub_subscription) - - getter = fn _path, _headers, _options -> - {:ok, - %Tesla.Env{ - status: 500, - body: "" - }} - end - - {:error, sub} = Websub.verify(sub, getter) - # Keep the current state. - assert sub.state == "requested" - end - - test "an incoming subscription request" do - user = insert(:user) - - data = %{ - "hub.callback" => "http://example.org/sub", - "hub.mode" => "subscribe", - "hub.topic" => Pleroma.Web.OStatus.feed_path(user), - "hub.secret" => "a random secret", - "hub.lease_seconds" => "100" - } - - {:ok, subscription} = Websub.incoming_subscription_request(user, data) - assert subscription.topic == Pleroma.Web.OStatus.feed_path(user) - assert subscription.state == "requested" - assert subscription.secret == "a random secret" - assert subscription.callback == "http://example.org/sub" - end - - test "an incoming subscription request for an existing subscription" do - user = insert(:user) - - sub = - insert(:websub_subscription, state: "accepted", topic: Pleroma.Web.OStatus.feed_path(user)) - - data = %{ - "hub.callback" => sub.callback, - "hub.mode" => "subscribe", - "hub.topic" => Pleroma.Web.OStatus.feed_path(user), - "hub.secret" => "a random secret", - "hub.lease_seconds" => "100" - } - - {:ok, subscription} = Websub.incoming_subscription_request(user, data) - assert subscription.topic == Pleroma.Web.OStatus.feed_path(user) - assert subscription.state == sub.state - assert subscription.secret == "a random secret" - assert subscription.callback == sub.callback - assert length(Repo.all(WebsubServerSubscription)) == 1 - assert subscription.id == sub.id - end - - def accepting_verifier(subscription) do - {:ok, %{subscription | state: "accepted"}} - end - - test "initiate a subscription for a given user and topic" do - subscriber = insert(:user) - user = insert(:user, %{info: %Pleroma.User.Info{topic: "some_topic", hub: "some_hub"}}) - - {:ok, websub} = Websub.subscribe(subscriber, user, &accepting_verifier/1) - assert websub.subscribers == [subscriber.ap_id] - assert websub.topic == "some_topic" - assert websub.hub == "some_hub" - assert is_binary(websub.secret) - assert websub.user == user - assert websub.state == "accepted" - end - - test "discovers the hub and canonical url" do - topic = "https://mastodon.social/users/lambadalambda.atom" - - {:ok, discovered} = Websub.gather_feed_data(topic) - - expected = %{ - "hub" => "https://mastodon.social/api/push", - "uri" => "https://mastodon.social/users/lambadalambda", - "nickname" => "lambadalambda", - "name" => "Critical Value", - "host" => "mastodon.social", - "bio" => "a cool dude.", - "avatar" => %{ - "type" => "Image", - "url" => [ - %{ - "href" => - "https://files.mastodon.social/accounts/avatars/000/000/264/original/1429214160519.gif?1492379244", - "mediaType" => "image/gif", - "type" => "Link" - } - ] - } - } - - assert expected == discovered - end - - test "calls the hub, requests topic" do - hub = "https://social.heldscal.la/main/push/hub" - topic = "https://social.heldscal.la/api/statuses/user_timeline/23211.atom" - websub = insert(:websub_client_subscription, %{hub: hub, topic: topic}) - - poster = fn ^hub, {:form, data}, _headers -> - assert Keyword.get(data, :"hub.mode") == "subscribe" - - assert Keyword.get(data, :"hub.callback") == - Helpers.websub_url( - Pleroma.Web.Endpoint, - :websub_subscription_confirmation, - websub.id - ) - - {:ok, %{status: 202}} - end - - task = Task.async(fn -> Websub.request_subscription(websub, poster) end) - - change = Ecto.Changeset.change(websub, %{state: "accepted"}) - {:ok, _} = Repo.update(change) - - {:ok, websub} = Task.await(task) - - assert websub.state == "accepted" - end - - test "rejects the subscription if it can't be accepted" do - hub = "https://social.heldscal.la/main/push/hub" - topic = "https://social.heldscal.la/api/statuses/user_timeline/23211.atom" - websub = insert(:websub_client_subscription, %{hub: hub, topic: topic}) - - poster = fn ^hub, {:form, _data}, _headers -> - {:ok, %{status: 202}} - end - - {:error, websub} = Websub.request_subscription(websub, poster, 1000) - assert websub.state == "rejected" - - websub = insert(:websub_client_subscription, %{hub: hub, topic: topic}) - - poster = fn ^hub, {:form, _data}, _headers -> - {:ok, %{status: 400}} - end - - {:error, websub} = Websub.request_subscription(websub, poster, 1000) - assert websub.state == "rejected" - end - - test "sign a text" do - signed = Websub.sign("secret", "text") - assert signed == "B8392C23690CCF871F37EC270BE1582DEC57A503" |> String.downcase() - - _signed = Websub.sign("secret", [["て"], ['す']]) - end - - describe "renewing subscriptions" do - test "it renews subscriptions that have less than a day of time left" do - day = 60 * 60 * 24 - now = NaiveDateTime.utc_now() - - still_good = - insert(:websub_client_subscription, %{ - valid_until: NaiveDateTime.add(now, 2 * day), - topic: "http://example.org/still_good", - hub: "http://example.org/still_good", - state: "accepted" - }) - - needs_refresh = - insert(:websub_client_subscription, %{ - valid_until: NaiveDateTime.add(now, day - 100), - topic: "http://example.org/needs_refresh", - hub: "http://example.org/needs_refresh", - state: "accepted" - }) - - _refresh = Websub.refresh_subscriptions() - - assert still_good == Repo.get(WebsubClientSubscription, still_good.id) - refute needs_refresh == Repo.get(WebsubClientSubscription, needs_refresh.id) - end - end -end |