diff options
Diffstat (limited to 'test/web')
117 files changed, 14876 insertions, 11298 deletions
diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index 40344f17e..ba2ce1dd9 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -1,32 +1,37 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 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) - - config_path = [:instance, :federating] - initial_setting = Pleroma.Config.get(config_path) - - Pleroma.Config.put(config_path, true) - on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end) - :ok end + clear_config_all([:instance, :federating], + do: Pleroma.Config.put([:instance, :federating], true) + ) + describe "/relay" do + clear_config([:instance, :allow_relay]) + test "with the relay active, it returns the relay user", %{conn: conn} do res = conn @@ -43,8 +48,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do |> get(activity_pub_path(conn, :relay)) |> json_response(404) |> assert - - Pleroma.Config.put([:instance, :allow_relay], true) end end @@ -107,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 @@ -177,21 +193,48 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do assert json_response(conn, 404) end - end - describe "/object/:uuid/likes" do - test "it returns the like activities in a collection", %{conn: conn} do - like = insert(:like_activity) - like_object_ap_id = Object.normalize(like).data["id"] - uuid = String.split(like_object_ap_id, "/") |> List.last() + test "it caches a response", %{conn: conn} do + note = insert(:note) + uuid = String.split(note.data["id"], "/") |> List.last() - result = + conn1 = conn |> put_req_header("accept", "application/activity+json") - |> get("/objects/#{uuid}/likes") - |> json_response(200) + |> get("/objects/#{uuid}") + + assert json_response(conn1, :ok) + assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"})) + + conn2 = + conn + |> put_req_header("accept", "application/activity+json") + |> get("/objects/#{uuid}") - assert List.first(result["first"]["orderedItems"])["id"] == like.data["id"] + assert json_response(conn1, :ok) == json_response(conn2, :ok) + assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"})) + end + + test "cached purged after object deletion", %{conn: conn} do + note = insert(:note) + uuid = String.split(note.data["id"], "/") |> List.last() + + conn1 = + conn + |> put_req_header("accept", "application/activity+json") + |> get("/objects/#{uuid}") + + assert json_response(conn1, :ok) + assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"})) + + Object.delete(note) + + conn2 = + conn + |> put_req_header("accept", "application/activity+json") + |> get("/objects/#{uuid}") + + assert "Not found" == json_response(conn2, :not_found) end end @@ -219,6 +262,51 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do assert json_response(conn, 404) end + + test "it caches a response", %{conn: conn} do + activity = insert(:note_activity) + uuid = String.split(activity.data["id"], "/") |> List.last() + + conn1 = + conn + |> put_req_header("accept", "application/activity+json") + |> get("/activities/#{uuid}") + + assert json_response(conn1, :ok) + assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"})) + + conn2 = + conn + |> put_req_header("accept", "application/activity+json") + |> get("/activities/#{uuid}") + + assert json_response(conn1, :ok) == json_response(conn2, :ok) + assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"})) + end + + test "cached purged after activity deletion", %{conn: conn} do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "cofe"}) + + uuid = String.split(activity.data["id"], "/") |> List.last() + + conn1 = + conn + |> put_req_header("accept", "application/activity+json") + |> get("/activities/#{uuid}") + + assert json_response(conn1, :ok) + assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"})) + + Activity.delete_all_by_object_ap_id(activity.object.data["id"]) + + conn2 = + conn + |> put_req_header("accept", "application/activity+json") + |> get("/activities/#{uuid}") + + assert "Not found" == json_response(conn2, :not_found) + end end describe "/inbox" do @@ -232,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 @@ -274,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 @@ -303,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 @@ -320,6 +490,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do assert json_response(conn, 403) end + test "it doesn't crash without an authenticated user", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> put_req_header("accept", "application/activity+json") + |> get("/users/#{user.nickname}/inbox") + + assert json_response(conn, 403) + end + test "it returns a note activity in a collection", %{conn: conn} do note_activity = insert(:direct_note_activity) note_object = Object.normalize(note_activity) @@ -329,7 +510,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do conn |> assign(:user, user) |> put_req_header("accept", "application/activity+json") - |> get("/users/#{user.nickname}/inbox") + |> get("/users/#{user.nickname}/inbox?page=true") assert response(conn, 200) =~ note_object.data["content"] end @@ -382,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 @@ -415,7 +598,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do conn = conn |> put_req_header("accept", "application/activity+json") - |> get("/users/#{user.nickname}/outbox") + |> get("/users/#{user.nickname}/outbox?page=true") assert response(conn, 200) =~ note_object.data["content"] end @@ -427,7 +610,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do conn = conn |> put_req_header("accept", "application/activity+json") - |> get("/users/#{user.nickname}/outbox") + |> get("/users/#{user.nickname}/outbox?page=true") assert response(conn, 200) =~ announce_activity.data["object"] end @@ -457,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 @@ -549,6 +733,34 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do end end + describe "/relay/followers" do + test "it returns relay followers", %{conn: conn} do + relay_actor = Relay.get_actor() + user = insert(:user) + User.follow(user, relay_actor) + + result = + conn + |> assign(:relay, true) + |> get("/relay/followers") + |> json_response(200) + + assert result["first"]["orderedItems"] == [user.ap_id] + end + end + + describe "/relay/following" do + test "it returns relay following", %{conn: conn} do + result = + conn + |> assign(:relay, true) + |> get("/relay/following") + |> json_response(200) + + assert result["first"]["orderedItems"] == [] + end + end + describe "/users/:nickname/followers" do test "it returns the followers in a collection", %{conn: conn} do user = insert(:user) @@ -565,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 = @@ -578,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 @@ -590,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) @@ -646,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) @@ -660,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 @@ -672,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) @@ -713,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 00adbc0f9..ff4604a52 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -4,14 +4,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do use Pleroma.DataCase + use Oban.Testing, repo: Pleroma.Repo + alias Pleroma.Activity alias Pleroma.Builders.ActivityBuilder - alias Pleroma.Instances + alias Pleroma.Notification alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.ActivityPub.Publisher alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.AdminAPI.AccountView alias Pleroma.Web.CommonAPI import Pleroma.Factory @@ -23,6 +25,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do :ok end + clear_config([:instance, :federating]) + describe "streaming out participations" do test "it streams them out" do user = insert(:user) @@ -38,9 +42,28 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do stream: fn _, _ -> nil end do ActivityPub.stream_out_participations(conversation.participations) - Enum.each(participations, fn participation -> - assert called(Pleroma.Web.Streamer.stream("participation", participation)) - end) + 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 @@ -89,17 +112,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) @@ -259,6 +348,42 @@ 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 "removes doubled 'to' recipients" do user = insert(:user) @@ -308,7 +433,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 @@ -362,7 +487,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] @@ -375,7 +500,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}) @@ -384,7 +509,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}) @@ -393,7 +518,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) @@ -420,7 +545,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!"}) @@ -443,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!"}) @@ -483,13 +608,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}) @@ -510,7 +670,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}) @@ -519,7 +679,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) @@ -540,6 +701,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert Enum.member?(activities, activity_one) end + test "doesn't return thread muted activities" do + user = insert(:user) + _activity_one = insert(:note_activity) + note_two = insert(:note, data: %{"context" => "suya.."}) + activity_two = insert(:note_activity, note: note_two) + + {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two) + + assert [_activity_one] = ActivityPub.fetch_activities([], %{"muting_user" => user}) + end + + test "returns thread muted activities when with_muted is set" do + user = insert(:user) + _activity_one = insert(:note_activity) + note_two = insert(:note, data: %{"context" => "suya.."}) + activity_two = insert(:note_activity, note: note_two) + + {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two) + + assert [_activity_two, _activity_one] = + ActivityPub.fetch_activities([], %{"muting_user" => user, "with_muted" => true}) + end + test "does include announces on request" do activity_three = insert(:note_activity) user = insert(:user) @@ -549,7 +733,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 @@ -589,48 +773,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) @@ -643,8 +840,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) @@ -654,7 +851,118 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do end end + describe "react to an object" do + test_with_mock "sends an activity to federation", Pleroma.Web.Federator, [:passthrough], [] do + Pleroma.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(Pleroma.Web.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"] == "EmojiReaction" + 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 + end + + describe "unreacting to an object" do + test_with_mock "sends an activity to federation", Pleroma.Web.Federator, [:passthrough], [] do + Pleroma.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(Pleroma.Web.Federator.publish(reaction_activity)) + + {:ok, unreaction_activity, _object} = + ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"]) + + assert called(Pleroma.Web.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 + 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) + 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)) + end + + test "returns exist activity if object already liked" do + note_activity = insert(:note_activity) + assert object_activity = Object.normalize(note_activity) + + user = insert(:user) + + {:ok, like_activity, _object} = ActivityPub.like(user, object_activity) + + {:ok, like_activity_exist, _object} = ActivityPub.like(user, object_activity) + assert like_activity == like_activity_exist + end + test "adds a like activity to the db" do note_activity = insert(:note_activity) assert object = Object.normalize(note_activity) @@ -679,18 +987,31 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert object.data["likes"] == [user.ap_id] assert object.data["like_count"] == 1 - [note_activity] = Activity.get_all_create_by_object_ap_id(object.data["id"]) - assert note_activity.data["object"]["like_count"] == 1 - {:ok, _like_activity, object} = ActivityPub.like(user_two, object) assert object.data["like_count"] == 2 - - [note_activity] = Activity.get_all_create_by_object_ap_id(object.data["id"]) - assert note_activity.data["object"]["like_count"] == 2 end end describe "unliking" do + test_with_mock "sends an activity to federation", Pleroma.Web.Federator, [:passthrough], [] do + Pleroma.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()) + + {:ok, _like_activity, object} = ActivityPub.like(user, object) + assert object.data["like_count"] == 1 + + {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object) + assert object.data["like_count"] == 0 + + assert called(Pleroma.Web.Federator.publish(unlike_activity)) + end + test "unliking a previously liked object" do note_activity = insert(:note_activity) object = Object.normalize(note_activity) @@ -703,10 +1024,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do {:ok, like_activity, object} = ActivityPub.like(user, object) assert object.data["like_count"] == 1 - {:ok, _, _, object} = ActivityPub.unlike(user, object) + {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object) assert object.data["like_count"] == 0 assert Activity.get_by_id(like_activity.id) == nil + assert note_activity.actor in unlike_activity.recipients end end @@ -731,6 +1053,39 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do 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 test "unannouncing a previously announced object" do note_activity = insert(:note_activity) @@ -749,7 +1104,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert unannounce_activity.data["to"] == [ User.ap_followers(user), - announce_activity.data["actor"] + object.data["actor"] ] assert unannounce_activity.data["type"] == "Undo" @@ -867,7 +1222,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), %{ @@ -899,7 +1254,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 @@ -953,6 +1308,21 @@ 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 + rewrite_policy = Pleroma.Config.get([:instance, :rewrite_policy]) + Pleroma.Config.put([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.DropPolicy) + + on_exit(fn -> Pleroma.Config.put([:instance, :rewrite_policy], rewrite_policy) end) + + 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 @@ -990,7 +1360,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"]) @@ -1000,7 +1370,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 @@ -1052,141 +1422,98 @@ 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 - end - - describe "publish_one/1" do - test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified", - Instances, - [:passthrough], - [] do - actor = insert(:user) - inbox = "http://200.site/users/nick1/inbox" - - assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) - - assert called(Instances.set_reachable(inbox)) + 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_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set", - Instances, - [:passthrough], - [] do - actor = insert(:user) - inbox = "http://200.site/users/nick1/inbox" - - assert {:ok, _} = - Publisher.publish_one(%{ - inbox: inbox, - json: "{}", - actor: actor, - id: 1, - unreachable_since: NaiveDateTime.utc_now() + 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 }) - assert called(Instances.set_reachable(inbox)) - end - - test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil", - Instances, - [:passthrough], - [] do - actor = insert(:user) - inbox = "http://200.site/users/nick1/inbox" - - assert {:ok, _} = - Publisher.publish_one(%{ - inbox: inbox, - json: "{}", - actor: actor, - id: 1, - unreachable_since: nil - }) - - refute called(Instances.set_reachable(inbox)) - end - - test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code", - Instances, - [:passthrough], - [] do - actor = insert(:user) - inbox = "http://404.site/users/nick1/inbox" - - assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) - - assert called(Instances.set_unreachable(inbox)) - end - - test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind", - Instances, - [:passthrough], - [] do - actor = insert(:user) - inbox = "http://connrefused.site/users/nick1/inbox" - - assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) + 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 called(Instances.set_unreachable(inbox)) + assert %Activity{ + actor: ^reporter_ap_id, + data: %{ + "type" => "Flag", + "content" => ^content, + "context" => ^context, + "object" => [^target_ap_id, ^note_obj] + } + } = activity end - test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable", - Instances, + 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 - actor = insert(:user) - inbox = "http://200.site/users/nick1/inbox" + {:ok, activity} = + ActivityPub.flag(%{ + actor: reporter, + context: context, + account: target_account, + statuses: [reported_activity], + content: content + }) - assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) + new_data = + put_in(activity.data, ["object"], [target_account.ap_id, reported_activity.data["id"]]) - refute called(Instances.set_unreachable(inbox)) - end - - test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`", - Instances, - [:passthrough], - [] 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() - }) - - refute called(Instances.set_unreachable(inbox)) + assert_called(Utils.maybe_federate(%{activity | data: new_data})) end end @@ -1237,4 +1564,207 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert result.id == activity.id end end + + describe "fetch_follow_information_for_user" do + test "syncronizes following/followers counters" do + user = + insert(:user, + local: false, + follower_address: "http://localhost:4001/users/fuser2/followers", + following_address: "http://localhost:4001/users/fuser2/following" + ) + + {:ok, info} = ActivityPub.fetch_follow_information_for_user(user) + assert info.follower_count == 527 + assert info.following_count == 267 + end + + test "detects hidden followers" do + mock(fn env -> + case env.url do + "http://localhost:4001/users/masto_closed/followers?page=1" -> + %Tesla.Env{status: 403, body: ""} + + _ -> + apply(HttpRequestMock, :request, [env]) + end + end) + + user = + insert(:user, + local: false, + follower_address: "http://localhost:4001/users/masto_closed/followers", + following_address: "http://localhost:4001/users/masto_closed/following" + ) + + {: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 + mock(fn env -> + case env.url do + "http://localhost:4001/users/masto_closed/following?page=1" -> + %Tesla.Env{status: 403, body: ""} + + _ -> + apply(HttpRequestMock, :request, [env]) + end + end) + + user = + insert(:user, + local: false, + follower_address: "http://localhost:4001/users/masto_closed/followers", + following_address: "http://localhost:4001/users/masto_closed/following" + ) + + {: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/mediaproxy_warming_policy_test.exs b/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs index 372e789be..95a809d25 100644 --- a/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs +++ b/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs @@ -6,6 +6,7 @@ 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/mrf_test.exs b/test/web/activity_pub/mrf/mrf_test.exs new file mode 100644 index 000000000..04709df17 --- /dev/null +++ b/test/web/activity_pub/mrf/mrf_test.exs @@ -0,0 +1,86 @@ +defmodule Pleroma.Web.ActivityPub.MRFTest do + use ExUnit.Case, async: true + use Pleroma.Tests.Helpers + alias Pleroma.Web.ActivityPub.MRF + + test "subdomains_regex/1" do + assert MRF.subdomains_regex(["unsafe.tld", "*.unsafe.tld"]) == [ + ~r/^unsafe.tld$/i, + ~r/^(.*\.)*unsafe.tld$/i + ] + end + + describe "subdomain_match/2" do + test "common domains" do + regexes = MRF.subdomains_regex(["unsafe.tld", "unsafe2.tld"]) + + assert regexes == [~r/^unsafe.tld$/i, ~r/^unsafe2.tld$/i] + + assert MRF.subdomain_match?(regexes, "unsafe.tld") + assert MRF.subdomain_match?(regexes, "unsafe2.tld") + + refute MRF.subdomain_match?(regexes, "example.com") + end + + test "wildcard domains with one subdomain" do + regexes = MRF.subdomains_regex(["*.unsafe.tld"]) + + assert regexes == [~r/^(.*\.)*unsafe.tld$/i] + + assert MRF.subdomain_match?(regexes, "unsafe.tld") + assert MRF.subdomain_match?(regexes, "sub.unsafe.tld") + refute MRF.subdomain_match?(regexes, "anotherunsafe.tld") + refute MRF.subdomain_match?(regexes, "unsafe.tldanother") + end + + test "wildcard domains with two subdomains" do + regexes = MRF.subdomains_regex(["*.unsafe.tld"]) + + assert regexes == [~r/^(.*\.)*unsafe.tld$/i] + + assert MRF.subdomain_match?(regexes, "unsafe.tld") + assert MRF.subdomain_match?(regexes, "sub.sub.unsafe.tld") + refute MRF.subdomain_match?(regexes, "sub.anotherunsafe.tld") + refute MRF.subdomain_match?(regexes, "sub.unsafe.tldanother") + end + + test "matches are case-insensitive" do + regexes = MRF.subdomains_regex(["UnSafe.TLD", "UnSAFE2.Tld"]) + + assert regexes == [~r/^UnSafe.TLD$/i, ~r/^UnSAFE2.Tld$/i] + + assert MRF.subdomain_match?(regexes, "UNSAFE.TLD") + assert MRF.subdomain_match?(regexes, "UNSAFE2.TLD") + assert MRF.subdomain_match?(regexes, "unsafe.tld") + assert MRF.subdomain_match?(regexes, "unsafe2.tld") + + refute MRF.subdomain_match?(regexes, "EXAMPLE.COM") + refute MRF.subdomain_match?(regexes, "example.com") + end + end + + describe "describe/0" do + clear_config([:instance, :rewrite_policy]) + + test "it works as expected with noop policy" do + expected = %{ + mrf_policies: ["NoOpPolicy"], + exclusions: false + } + + {:ok, ^expected} = MRF.describe() + end + + test "it works as expected with mock policy" do + Pleroma.Config.put([:instance, :rewrite_policy], [MRFModuleMock]) + + expected = %{ + mrf_policies: ["MRFModuleMock"], + mrf_module_mock: "some config data", + exclusions: false + } + + {:ok, ^expected} = MRF.describe() + end + end +end 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/reject_non_public_test.exs b/test/web/activity_pub/mrf/reject_non_public_test.exs index fdf6b245e..fc1d190bb 100644 --- a/test/web/activity_pub/mrf/reject_non_public_test.exs +++ b/test/web/activity_pub/mrf/reject_non_public_test.exs @@ -8,12 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublicTest do alias Pleroma.Web.ActivityPub.MRF.RejectNonPublic - setup do - policy = Pleroma.Config.get([:mrf_rejectnonpublic]) - on_exit(fn -> Pleroma.Config.put([:mrf_rejectnonpublic], policy) end) - - :ok - end + clear_config([:mrf_rejectnonpublic]) describe "public message" do test "it's allowed when address is public" do diff --git a/test/web/activity_pub/mrf/simple_policy_test.exs b/test/web/activity_pub/mrf/simple_policy_test.exs index 0fd68e103..df0f223f8 100644 --- a/test/web/activity_pub/mrf/simple_policy_test.exs +++ b/test/web/activity_pub/mrf/simple_policy_test.exs @@ -8,9 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do alias Pleroma.Config alias Pleroma.Web.ActivityPub.MRF.SimplePolicy - setup do - orig = Config.get!(:mrf_simple) - + clear_config([:mrf_simple]) do Config.put(:mrf_simple, media_removal: [], media_nsfw: [], @@ -21,10 +19,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do avatar_removal: [], banner_removal: [] ) - - on_exit(fn -> - Config.put(:mrf_simple, orig) - end) end describe "when :media_removal" do @@ -49,6 +43,19 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do assert SimplePolicy.filter(local_message) == {:ok, local_message} end + + test "match with wildcard domain" do + Config.put([:mrf_simple, :media_removal], ["*.remote.instance"]) + media_message = build_media_message() + local_message = build_local_message() + + assert SimplePolicy.filter(media_message) == + {:ok, + media_message + |> Map.put("object", Map.delete(media_message["object"], "attachment"))} + + assert SimplePolicy.filter(local_message) == {:ok, local_message} + end end describe "when :media_nsfw" do @@ -74,6 +81,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do assert SimplePolicy.filter(local_message) == {:ok, local_message} end + + test "match with wildcard domain" do + Config.put([:mrf_simple, :media_nsfw], ["*.remote.instance"]) + media_message = build_media_message() + local_message = build_local_message() + + assert SimplePolicy.filter(media_message) == + {:ok, + media_message + |> put_in(["object", "tag"], ["foo", "nsfw"]) + |> put_in(["object", "sensitive"], true)} + + assert SimplePolicy.filter(local_message) == {:ok, local_message} + end end defp build_media_message do @@ -106,6 +127,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do assert SimplePolicy.filter(report_message) == {:reject, nil} assert SimplePolicy.filter(local_message) == {:ok, local_message} end + + test "match with wildcard domain" do + Config.put([:mrf_simple, :report_removal], ["*.remote.instance"]) + report_message = build_report_message() + local_message = build_local_message() + + assert SimplePolicy.filter(report_message) == {:reject, nil} + assert SimplePolicy.filter(local_message) == {:ok, local_message} + end end defp build_report_message do @@ -146,6 +176,27 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do assert SimplePolicy.filter(local_message) == {:ok, local_message} end + test "match with wildcard domain" do + {actor, ftl_message} = build_ftl_actor_and_message() + + ftl_message_actor_host = + ftl_message + |> Map.fetch!("actor") + |> URI.parse() + |> Map.fetch!(:host) + + Config.put([:mrf_simple, :federated_timeline_removal], ["*." <> ftl_message_actor_host]) + local_message = build_local_message() + + assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message) + assert actor.follower_address in ftl_message["to"] + refute actor.follower_address in ftl_message["cc"] + refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"] + assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"] + + assert SimplePolicy.filter(local_message) == {:ok, local_message} + end + test "has a matching host but only as:Public in to" do {_actor, ftl_message} = build_ftl_actor_and_message() @@ -185,13 +236,29 @@ 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() assert SimplePolicy.filter(remote_message) == {:reject, nil} end + + 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 @@ -205,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() @@ -215,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() @@ -224,6 +291,24 @@ 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 "activity matches with wildcard domain" do + Config.put([:mrf_simple, :accept], ["*.remote.instance"]) + + local_message = build_local_message() + remote_message = build_remote_message() + + 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 @@ -251,6 +336,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do refute filtered["icon"] end + + test "match with wildcard domain" do + Config.put([:mrf_simple, :avatar_removal], ["*.remote.instance"]) + + remote_user = build_remote_user() + {:ok, filtered} = SimplePolicy.filter(remote_user) + + refute filtered["icon"] + end end describe "when :banner_removal" do @@ -278,6 +372,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do refute filtered["image"] end + + test "match with wildcard domain" do + Config.put([:mrf_simple, :banner_removal], ["*.remote.instance"]) + + remote_user = build_remote_user() + {:ok, filtered} = SimplePolicy.filter(remote_user) + + refute filtered["image"] + end end defp build_local_message 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 6519e2398..72084c0fd 100644 --- a/test/web/activity_pub/mrf/user_allowlist_policy_test.exs +++ b/test/web/activity_pub/mrf/user_allowlist_policy_test.exs @@ -7,12 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicyTest do alias Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy - setup do - policy = Pleroma.Config.get([:mrf_user_allowlist]) || [] - on_exit(fn -> Pleroma.Config.put([:mrf_user_allowlist], policy) end) - - :ok - end + clear_config([:mrf_user_allowlist, :localhost]) test "pass filter if allow list is empty" do actor = insert(:user) diff --git a/test/web/activity_pub/mrf/vocabulary_policy_test.exs b/test/web/activity_pub/mrf/vocabulary_policy_test.exs new file mode 100644 index 000000000..38309f9f1 --- /dev/null +++ b/test/web/activity_pub/mrf/vocabulary_policy_test.exs @@ -0,0 +1,106 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicyTest do + use Pleroma.DataCase + + alias Pleroma.Web.ActivityPub.MRF.VocabularyPolicy + + describe "accept" do + clear_config([:mrf_vocabulary, :accept]) + + test "it accepts based on parent activity type" do + Pleroma.Config.put([:mrf_vocabulary, :accept], ["Like"]) + + message = %{ + "type" => "Like", + "object" => "whatever" + } + + {:ok, ^message} = VocabularyPolicy.filter(message) + end + + test "it accepts based on child object type" do + Pleroma.Config.put([:mrf_vocabulary, :accept], ["Create", "Note"]) + + message = %{ + "type" => "Create", + "object" => %{ + "type" => "Note", + "content" => "whatever" + } + } + + {:ok, ^message} = VocabularyPolicy.filter(message) + end + + test "it does not accept disallowed child objects" do + Pleroma.Config.put([:mrf_vocabulary, :accept], ["Create", "Note"]) + + message = %{ + "type" => "Create", + "object" => %{ + "type" => "Article", + "content" => "whatever" + } + } + + {:reject, nil} = VocabularyPolicy.filter(message) + end + + test "it does not accept disallowed parent types" do + Pleroma.Config.put([:mrf_vocabulary, :accept], ["Announce", "Note"]) + + message = %{ + "type" => "Create", + "object" => %{ + "type" => "Note", + "content" => "whatever" + } + } + + {:reject, nil} = VocabularyPolicy.filter(message) + end + end + + describe "reject" do + clear_config([:mrf_vocabulary, :reject]) + + test "it rejects based on parent activity type" do + Pleroma.Config.put([:mrf_vocabulary, :reject], ["Like"]) + + message = %{ + "type" => "Like", + "object" => "whatever" + } + + {:reject, nil} = VocabularyPolicy.filter(message) + end + + test "it rejects based on child object type" do + Pleroma.Config.put([:mrf_vocabulary, :reject], ["Note"]) + + message = %{ + "type" => "Create", + "object" => %{ + "type" => "Note", + "content" => "whatever" + } + } + + {:reject, nil} = VocabularyPolicy.filter(message) + end + + test "it passes through objects that aren't disallowed" do + Pleroma.Config.put([:mrf_vocabulary, :reject], ["Like"]) + + message = %{ + "type" => "Announce", + "object" => "whatever" + } + + {:ok, ^message} = VocabularyPolicy.filter(message) + end + end +end diff --git a/test/web/activity_pub/publisher_test.exs b/test/web/activity_pub/publisher_test.exs new file mode 100644 index 000000000..015af19ab --- /dev/null +++ b/test/web/activity_pub/publisher_test.exs @@ -0,0 +1,349 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.PublisherTest do + 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" + + setup do + mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + :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, %{ + source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}} + }) + + activity = %Activity{ + data: %{"to" => [@as_public], "cc" => [user.follower_address]} + } + + assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox" + end + + test "it returns sharedInbox for messages involving as:Public in cc" do + user = + insert(:user, %{ + source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}} + }) + + activity = %Activity{ + data: %{"cc" => [@as_public], "to" => [user.follower_address]} + } + + assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox" + end + + test "it returns sharedInbox for messages involving multiple recipients in to" do + user = + insert(:user, %{ + source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}} + }) + + user_two = insert(:user) + user_three = insert(:user) + + activity = %Activity{ + data: %{"cc" => [], "to" => [user.ap_id, user_two.ap_id, user_three.ap_id]} + } + + assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox" + end + + test "it returns sharedInbox for messages involving multiple recipients in cc" do + user = + insert(:user, %{ + source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}} + }) + + user_two = insert(:user) + user_three = insert(:user) + + activity = %Activity{ + data: %{"to" => [], "cc" => [user.ap_id, user_two.ap_id, user_three.ap_id]} + } + + assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox" + end + + test "it returns sharedInbox for messages involving multiple recipients in total" do + user = + insert(:user, + source_data: %{ + "inbox" => "http://example.com/personal-inbox", + "endpoints" => %{"sharedInbox" => "http://example.com/inbox"} + } + ) + + user_two = insert(:user) + + activity = %Activity{ + data: %{"to" => [user_two.ap_id], "cc" => [user.ap_id]} + } + + assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox" + end + + test "it returns inbox for messages involving single recipients in total" do + user = + insert(:user, + source_data: %{ + "inbox" => "http://example.com/personal-inbox", + "endpoints" => %{"sharedInbox" => "http://example.com/inbox"} + } + ) + + activity = %Activity{ + data: %{"to" => [user.ap_id], "cc" => []} + } + + assert Publisher.determine_inbox(activity, user) == "http://example.com/personal-inbox" + end + end + + describe "publish_one/1" do + test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified", + Instances, + [:passthrough], + [] do + actor = insert(:user) + inbox = "http://200.site/users/nick1/inbox" + + assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) + + assert called(Instances.set_reachable(inbox)) + end + + test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set", + Instances, + [:passthrough], + [] do + actor = insert(:user) + inbox = "http://200.site/users/nick1/inbox" + + assert {:ok, _} = + Publisher.publish_one(%{ + inbox: inbox, + json: "{}", + actor: actor, + id: 1, + unreachable_since: NaiveDateTime.utc_now() + }) + + assert called(Instances.set_reachable(inbox)) + end + + test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil", + Instances, + [:passthrough], + [] do + actor = insert(:user) + inbox = "http://200.site/users/nick1/inbox" + + assert {:ok, _} = + Publisher.publish_one(%{ + inbox: inbox, + json: "{}", + actor: actor, + id: 1, + unreachable_since: nil + }) + + refute called(Instances.set_reachable(inbox)) + end + + test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code", + Instances, + [:passthrough], + [] do + actor = insert(:user) + inbox = "http://404.site/users/nick1/inbox" + + assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) + + assert called(Instances.set_unreachable(inbox)) + end + + test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind", + Instances, + [:passthrough], + [] do + actor = insert(:user) + inbox = "http://connrefused.site/users/nick1/inbox" + + 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 + + test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable", + Instances, + [:passthrough], + [] do + actor = insert(:user) + inbox = "http://200.site/users/nick1/inbox" + + assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) + + refute called(Instances.set_unreachable(inbox)) + end + + test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`", + Instances, + [:passthrough], + [] do + actor = insert(:user) + inbox = "http://connrefused.site/users/nick1/inbox" + + 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 + end + + describe "publish/2" do + test_with_mock "publishes an activity with BCC to all relevant peers.", + Pleroma.Web.Federator.Publisher, + [:passthrough], + [] do + follower = + insert(:user, + local: false, + source_data: %{"inbox" => "https://domain.com/users/nick1/inbox"}, + ap_enabled: true + ) + + actor = insert(:user, follower_address: follower.ap_id) + user = insert(:user) + + {:ok, _follower_one} = Pleroma.User.follow(follower, actor) + actor = refresh_record(actor) + + note_activity = + insert(:note_activity, + recipients: [follower.ap_id], + data_attrs: %{"bcc" => [user.ap_id]} + ) + + res = Publisher.publish(actor, note_activity) + assert res == :ok + + assert called( + Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{ + inbox: "https://domain.com/users/nick1/inbox", + 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 21a63c493..98dc78f46 100644 --- a/test/web/activity_pub/relay_test.exs +++ b/test/web/activity_pub/relay_test.exs @@ -1,15 +1,125 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.RelayTest do use Pleroma.DataCase + 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 + test "gets an actor for the relay" do user = Relay.get_actor() + 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 capture_log(fn -> + {:error, _} = Relay.follow("test-ap-id") + end) =~ "Could not decode user at fetch" + end + + test "returns activity" do + user = insert(:user) + service_actor = Relay.get_actor() + assert {:ok, %Activity{} = activity} = Relay.follow(user.ap_id) + assert activity.actor == "#{Pleroma.Web.Endpoint.url()}/relay" + assert user.ap_id in activity.recipients + assert activity.data["type"] == "Follow" + assert activity.data["actor"] == service_actor.ap_id + assert activity.data["object"] == user.ap_id + end + end + + describe "unfollow/1" do + test "returns errors when user not found" do + assert capture_log(fn -> + {:error, _} = Relay.unfollow("test-ap-id") + end) =~ "Could not decode user at fetch" + end + + test "returns activity" do + user = insert(:user) + service_actor = Relay.get_actor() + ActivityPub.follow(service_actor, user) + Pleroma.User.follow(service_actor, user) + 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 User.following(service_actor) + end + end + + describe "publish/1" do + clear_config([:instance, :federating]) + + test "returns error when activity not `Create` type" do + activity = insert(:like_activity) + assert Relay.publish(activity) == {:error, "Not implemented"} + end + + test "returns error when activity not public" do + activity = insert(:direct_note_activity) + assert Relay.publish(activity) == {:error, false} + end + + test "returns error when object is unknown" do + activity = + insert(:note_activity, + data: %{ + "type" => "Create", + "object" => "http://mastodon.example.org/eee/99541947525187367" + } + ) + + 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", + Pleroma.Web.Federator, + [:passthrough], + [] do + Pleroma.Config.put([:instance, :federating], true) + service_actor = Relay.get_actor() + note = insert(:note_activity) + assert {:ok, %Activity{} = activity, %Object{} = obj} = Relay.publish(note) + 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)) + end - assert user.ap_id =~ "/relay" + test_with_mock "returns announce activity and not publish to federate", + Pleroma.Web.Federator, + [:passthrough], + [] do + Pleroma.Config.put([:instance, :federating], false) + service_actor = Relay.get_actor() + note = insert(:note_activity) + assert {:ok, %Activity{} = activity, %Object{} = obj} = Relay.publish(note) + 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)) + 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 857d65564..1c88b05c2 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-2019 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do @@ -19,6 +19,25 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do end describe "handle_incoming" do + test "it works for osada follow request" do + user = insert(:user) + + data = + File.read!("test/fixtures/osada-follow-activity.json") + |> Poison.decode!() + |> Map.put("object", user.ap_id) + + {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == "https://apfed.club/channel/indio" + assert data["type"] == "Follow" + assert data["id"] == "https://apfed.club/follow/9" + + activity = Repo.get(Activity, activity.id) + assert activity.data["state"] == "accept" + assert User.following?(User.get_cached_by_ap_id(data["actor"]), user) + end + test "it works for incoming follow requests" do user = insert(:user) @@ -39,7 +58,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") @@ -59,7 +78,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 @@ -109,7 +128,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 a1f5f6e36..5da358c43 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do @@ -7,13 +7,12 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do 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 @@ -24,6 +23,8 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do :ok end + clear_config([:instance, :max_remote_account_fields]) + describe "handle_incoming" do test "it ignores an incoming notice if we already have it" do activity = insert(:note_activity) @@ -38,6 +39,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert activity == returned_activity end + @tag capture_log: true test "it fetches replied-to activities if we don't have them" do data = File.read!("test/fixtures/mastodon-post-activity.json") @@ -100,7 +102,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert capture_log(fn -> {:ok, _returned_activity} = Transmogrifier.handle_incoming(data) - end) =~ "[error] Couldn't fetch \"\"https://404.site/whatever\"\", error: nil" + end) =~ "[error] Couldn't fetch \"https://404.site/whatever\", error: nil" end test "it works for incoming notices" do @@ -145,7 +147,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 @@ -174,6 +176,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) @@ -309,6 +340,80 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert data["object"] == activity.data["object"] end + test "it works for incoming misskey likes, turning them into EmojiReactions" 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"] == "EmojiReaction" + 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 EmojiReactions" 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"] == "EmojiReaction" + 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"] == "EmojiReaction" + assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2" + assert data["object"] == activity.data["object"] + assert data["content"] == "👌" + 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"}) @@ -346,6 +451,31 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2" end + test "it works for incoming unlikes with an existing like activity and a compact object" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"}) + + like_data = + File.read!("test/fixtures/mastodon-like.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data) + + data = + File.read!("test/fixtures/mastodon-undo-like.json") + |> Poison.decode!() + |> Map.put("object", like_data["id"]) + |> Map.put("actor", like_data["actor"]) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == "http://mastodon.example.org/users/admin" + assert data["type"] == "Undo" + assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo" + assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2" + end + test "it works for incoming announces" do data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!() @@ -385,6 +515,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"}) @@ -450,6 +608,41 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert !is_nil(data["cc"]) end + test "it strips internal likes" do + data = + File.read!("test/fixtures/mastodon-post-activity.json") + |> Poison.decode!() + + likes = %{ + "first" => + "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1", + "id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes", + "totalItems" => 3, + "type" => "OrderedCollection" + } + + object = Map.put(data["object"], "likes", likes) + data = Map.put(data, "object", object) + + {:ok, %Activity{object: object}} = Transmogrifier.handle_incoming(data) + + 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!() @@ -468,6 +661,8 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data) + assert data["id"] == update_data["id"] + user = User.get_cached_by_ap_id(data["actor"]) assert user.name == "gargle" @@ -478,7 +673,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" @@ -488,6 +683,99 @@ 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" + |> File.read!() + |> Poison.decode!() + |> Transmogrifier.handle_incoming() + + user = User.get_cached_by_ap_id(activity.actor) + + assert User.fields(user) == [ + %{"name" => "foo", "value" => "bar"}, + %{"name" => "foo1", "value" => "bar1"} + ] + + update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!() + + object = + update_data["object"] + |> Map.put("actor", user.ap_id) + |> Map.put("id", user.ap_id) + + update_data = + update_data + |> Map.put("actor", user.ap_id) + |> Map.put("object", object) + + {:ok, _update_activity} = Transmogrifier.handle_incoming(update_data) + + user = User.get_cached_by_ap_id(user.ap_id) + + assert User.fields(user) == [ + %{"name" => "foo", "value" => "updated"}, + %{"name" => "foo1", "value" => "updated"} + ] + + Pleroma.Config.put([:instance, :max_remote_account_fields], 2) + + update_data = + put_in(update_data, ["object", "attachment"], [ + %{"name" => "foo", "type" => "PropertyValue", "value" => "bar"}, + %{"name" => "foo11", "type" => "PropertyValue", "value" => "bar11"}, + %{"name" => "foo22", "type" => "PropertyValue", "value" => "bar22"} + ]) + + {:ok, _} = Transmogrifier.handle_incoming(update_data) + + user = User.get_cached_by_ap_id(user.ap_id) + + assert User.fields(user) == [ + %{"name" => "foo", "value" => "updated"}, + %{"name" => "foo1", "value" => "updated"} + ] + + update_data = put_in(update_data, ["object", "attachment"], []) + + {:ok, _} = Transmogrifier.handle_incoming(update_data) + + user = User.get_cached_by_ap_id(user.ap_id) + + assert User.fields(user) == [] + end + test "it works for incoming update activities which lock the account" do data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() @@ -508,11 +796,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") @@ -525,11 +814,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 @@ -550,11 +842,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") @@ -563,6 +856,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 @@ -575,7 +869,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 @@ -633,6 +930,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) @@ -735,6 +1051,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 @@ -742,7 +1060,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) @@ -764,7 +1082,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) @@ -784,7 +1102,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") @@ -803,7 +1121,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") @@ -822,7 +1140,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) @@ -839,6 +1157,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) @@ -847,7 +1166,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) @@ -916,10 +1235,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 @@ -927,14 +1254,95 @@ 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 "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) @@ -991,32 +1399,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) @@ -1061,14 +1443,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert is_nil(modified["object"]["announcements"]) assert is_nil(modified["object"]["announcement_count"]) assert is_nil(modified["object"]["context_id"]) - end - - test "it adds like collection to object" do - activity = insert(:note_activity) - {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) - - assert modified["object"]["likes"]["type"] == "OrderedCollection" - assert modified["object"]["likes"]["totalItems"] == 0 + assert is_nil(modified["object"]["likes"]) end test "the directMessage flag is present" do @@ -1110,6 +1485,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 @@ -1122,23 +1511,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 @@ -1159,7 +1551,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 @@ -1167,23 +1559,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 @@ -1209,7 +1586,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 @@ -1222,7 +1601,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 @@ -1235,7 +1616,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 @@ -1374,31 +1757,274 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do end end - test "update_following_followers_counters/1" do - user1 = - insert(:user, - local: false, - follower_address: "http://localhost:4001/users/masto_closed/followers", - following_address: "http://localhost:4001/users/masto_closed/following" - ) + 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" - user2 = - insert(:user, - local: false, - follower_address: "http://localhost:4001/users/fuser2/followers", - following_address: "http://localhost:4001/users/fuser2/following" - ) + assert modified_object["conversation"] == + "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26" - Transmogrifier.update_following_followers_counters(user1) - Transmogrifier.update_following_followers_counters(user2) + assert modified_object["context"] == + "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26" + end + end - %{follower_count: followers, following_count: following} = User.get_cached_user_info(user1) - assert followers == 437 - assert following == 152 + 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" + } + } - %{follower_count: followers, following_count: following} = User.get_cached_user_info(user2) + assert Transmogrifier.fix_url(object) == %{ + "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4" + } + end - assert followers == 527 - assert following == 267 + 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 end diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs index ca5f057a7..211fa6c95 100644 --- a/test/web/activity_pub/utils_test.exs +++ b/test/web/activity_pub/utils_test.exs @@ -10,10 +10,13 @@ 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 + require Pleroma.Constants + describe "fetch the latest Follow" do test "fetches the latest Follow activity" do %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity) @@ -85,6 +88,46 @@ 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 + test "returns data for unlike activity" do + user = insert(:user) + like_activity = insert(:like_activity, data_attrs: %{"context" => "test context"}) + + object = Object.normalize(like_activity.data["object"]) + + assert Utils.make_unlike_data(user, like_activity, nil) == %{ + "type" => "Undo", + "actor" => user.ap_id, + "object" => like_activity.data, + "to" => [user.follower_address, object.data["actor"]], + "cc" => [Pleroma.Constants.as_public()], + "context" => like_activity.data["context"] + } + + assert Utils.make_unlike_data(user, like_activity, "9mJEZK0tky1w2xD2vY") == %{ + "type" => "Undo", + "actor" => user.ap_id, + "object" => like_activity.data, + "to" => [user.follower_address, object.data["actor"]], + "cc" => [Pleroma.Constants.as_public()], + "context" => like_activity.data["context"], + "id" => "9mJEZK0tky1w2xD2vY" + } + end end describe "make_like_data" do @@ -255,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) @@ -272,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) @@ -295,8 +338,315 @@ 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 + + describe "update_element_in_object/3" do + test "updates likes" do + user = insert(:user) + activity = insert(:note_activity) + object = Object.normalize(activity) + + assert {:ok, updated_object} = + Utils.update_element_in_object( + "like", + [user.ap_id], + object + ) + + assert updated_object.data["likes"] == [user.ap_id] + assert updated_object.data["like_count"] == 1 + end + end + + describe "add_like_to_object/2" do + test "add actor to likes" do + user = insert(:user) + user2 = insert(:user) + object = insert(:note) + + assert {:ok, updated_object} = + Utils.add_like_to_object( + %Activity{data: %{"actor" => user.ap_id}}, + object + ) + + assert updated_object.data["likes"] == [user.ap_id] + assert updated_object.data["like_count"] == 1 + + assert {:ok, updated_object2} = + Utils.add_like_to_object( + %Activity{data: %{"actor" => user2.ap_id}}, + updated_object + ) + + assert updated_object2.data["likes"] == [user2.ap_id, user.ap_id] + assert updated_object2.data["like_count"] == 2 + end + end + + describe "remove_like_from_object/2" do + test "removes ap_id from likes" do + user = insert(:user) + user2 = insert(:user) + object = insert(:note, data: %{"likes" => [user.ap_id, user2.ap_id], "like_count" => 2}) + + assert {:ok, updated_object} = + Utils.remove_like_from_object( + %Activity{data: %{"actor" => user.ap_id}}, + object + ) + + assert updated_object.data["likes"] == [user2.ap_id] + assert updated_object.data["like_count"] == 1 + end + end + + describe "get_existing_like/2" do + test "fetches existing like" do + note_activity = insert(:note_activity) + assert object = Object.normalize(note_activity) + + user = insert(:user) + refute Utils.get_existing_like(user.ap_id, object) + {:ok, like_activity, _object} = ActivityPub.like(user, object) + + 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/user_view_test.exs b/test/web/activity_pub/views/user_view_test.exs index 86254117f..8374b8d23 100644 --- a/test/web/activity_pub/views/user_view_test.exs +++ b/test/web/activity_pub/views/user_view_test.exs @@ -22,6 +22,37 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do assert String.contains?(result["publicKey"]["publicKeyPem"], "BEGIN PUBLIC KEY") end + test "Renders profile fields" do + fields = [ + %{"name" => "foo", "value" => "bar"} + ] + + {:ok, user} = + insert(:user) + |> User.upgrade_changeset(%{fields: fields}) + |> User.update_and_set_cache() + + assert %{ + "attachment" => [%{"name" => "foo", "type" => "PropertyValue", "value" => "bar"}] + } = 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) @@ -33,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) @@ -45,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) @@ -90,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 @@ -102,9 +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 "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 + + test "activity collection page aginates correctly" do + user = insert(:user) + + posts = + for i <- 0..25 do + {:ok, activity} = CommonAPI.post(user, %{"status" => "post #{i}"}) + activity + end + + # outbox sorts chronologically, newest first, with ten per page + posts = Enum.reverse(posts) + + %{"next" => next_url} = + UserView.render("activity_collection_page.json", %{ + iri: "#{user.ap_id}/outbox", + activities: Enum.take(posts, 10) + }) + + 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(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..4c2e0d207 100644 --- a/test/web/activity_pub/visibilty_test.exs +++ b/test/web/activity_pub/visibilty_test.exs @@ -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 ee48b752c..5c767219a 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -1,58 +1,324 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 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 alias Pleroma.Activity + 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 + Pleroma.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 + Pleroma.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 ModerationLog.get_log_entry_message(log_entry) == + "@#{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" => [ + %{ + "nickname" => "lain", + "email" => "lain@example.org", + "password" => "test" + }, + %{ + "nickname" => "lain2", + "email" => "lain2@example.org", + "password" => "test" + } + ] + }) + + response = json_response(conn, 200) |> Enum.map(&Map.get(&1, "type")) + assert response == ["success", "success"] + + log_entry = Repo.one(ModerationLog) + + assert ["lain", "lain2"] -- Enum.map(log_entry.data["subjects"], & &1["nickname"]) == [] + end + + test "Cannot create user with existing email", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> put_req_header("accept", "application/json") + |> post("/api/pleroma/admin/users", %{ + "users" => [ + %{ + "nickname" => "lain", + "email" => user.email, + "password" => "test" + } + ] + }) + + assert json_response(conn, 409) == [ + %{ + "code" => 409, + "data" => %{ + "email" => user.email, + "nickname" => "lain" + }, + "error" => "email has already been taken", + "type" => "error" + } + ] + end + + test "Cannot create user with existing nickname", %{conn: conn} do + user = insert(:user) + + conn = + conn |> put_req_header("accept", "application/json") |> post("/api/pleroma/admin/users", %{ - "nickname" => "lain", - "email" => "lain@example.org", - "password" => "test" + "users" => [ + %{ + "nickname" => user.nickname, + "email" => "someuser@plerama.social", + "password" => "test" + } + ] }) - assert json_response(conn, 200) == "lain" + assert json_response(conn, 409) == [ + %{ + "code" => 409, + "data" => %{ + "email" => "someuser@plerama.social", + "nickname" => user.nickname + }, + "error" => "nickname has already been taken", + "type" => "error" + } + ] + end + + test "Multiple user creation works in transaction", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> put_req_header("accept", "application/json") + |> post("/api/pleroma/admin/users", %{ + "users" => [ + %{ + "nickname" => "newuser", + "email" => "newuser@pleroma.social", + "password" => "test" + }, + %{ + "nickname" => "lain", + "email" => user.email, + "password" => "test" + } + ] + }) + + assert json_response(conn, 409) == [ + %{ + "code" => 409, + "data" => %{ + "email" => user.email, + "nickname" => "lain" + }, + "error" => "email has already been taken", + "type" => "error" + }, + %{ + "code" => 409, + "data" => %{ + "email" => "newuser@pleroma.social", + "nickname" => "newuser" + }, + "error" => "", + "type" => "error" + } + ] + + assert User.get_by_nickname("newuser") === nil end end 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, @@ -62,33 +328,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, @@ -99,19 +360,22 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do follower = User.get_cached_by_id(follower.id) assert User.following?(follower, user) + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} made @#{follower.nickname} follow @#{user.nickname}" end 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, @@ -122,24 +386,26 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do follower = User.get_cached_by_id(follower.id) refute User.following?(follower, user) + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} made @#{follower.nickname} unfollow @#{user.nickname}" end 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, user1: user1, user2: user2, user3: user3} @@ -147,12 +413,25 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do test "it appends specified tags to users with specified nicknames", %{ conn: conn, + admin: admin, user1: user1, user2: user2 } do assert json_response(conn, :no_content) assert User.get_cached_by_id(user1.id).tags == ["x", "foo", "bar"] assert User.get_cached_by_id(user2.id).tags == ["y", "foo", "bar"] + + log_entry = Repo.one(ModerationLog) + + users = + [user1.nickname, user2.nickname] + |> Enum.map(&"@#{&1}") + |> Enum.join(", ") + + tags = ["foo", "bar"] |> Enum.join(", ") + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} added tags: #{tags} to users: #{users}" end test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do @@ -162,20 +441,17 @@ 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, user1: user1, user2: user2, user3: user3} @@ -183,12 +459,25 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do test "it removes specified tags from users with specified nicknames", %{ conn: conn, + admin: admin, user1: user1, user2: user2 } do assert json_response(conn, :no_content) assert User.get_cached_by_id(user1.id).tags == [] assert User.get_cached_by_id(user2.id).tags == ["y"] + + log_entry = Repo.one(ModerationLog) + + users = + [user1.nickname, user2.nickname] + |> Enum.map(&"@#{&1}") + |> Enum.join(", ") + + tags = ["x", "z"] |> Enum.join(", ") + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} removed tags: #{tags} from users: #{users}" end test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do @@ -198,12 +487,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/") @@ -213,115 +499,106 @@ 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") assert json_response(conn, 200) == %{ "is_admin" => true } - 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}) - conn = - build_conn() - |> assign(:user, admin) - |> put_req_header("accept", "application/json") - |> delete("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin") + log_entry = Repo.one(ModerationLog) - assert json_response(conn, 200) == %{ - "is_admin" => false - } + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} made @#{user.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 POST, can add to a permission group (multiple)", %{admin: admin, conn: conn} do + user_one = insert(:user) + user_two = insert(:user) conn = conn - |> assign(:user, admin) |> put_req_header("accept", "application/json") + |> post("/api/pleroma/admin/users/permission_group/admin", %{ + nicknames: [user_one.nickname, user_two.nickname] + }) - %{conn: conn} - end - - test "deactivates the user", %{conn: conn} do - user = insert(:user) + assert json_response(conn, 200) == %{"is_admin" => true} - conn = - conn - |> put("/api/pleroma/admin/users/#{user.nickname}/activation_status", %{status: false}) + log_entry = Repo.one(ModerationLog) - user = User.get_cached_by_id(user.id) - assert user.info.deactivated == true - assert json_response(conn, :no_content) + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} made @#{user_one.nickname}, @#{user_two.nickname} admin" end - test "activates the user", %{conn: conn} do - user = insert(:user, info: %{deactivated: true}) + test "/:right DELETE, can remove from a permission group", %{admin: admin, conn: conn} do + user = 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/#{user.nickname}/permission_group/admin") - 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} revoked admin role from @#{user.nickname}" end - test "returns 403 when requested by a non-admin", %{conn: conn} do - user = insert(:user) + 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 - |> assign(:user, user) - |> put("/api/pleroma/admin/users/#{user.nickname}/activation_status", %{status: false}) + |> put_req_header("accept", "application/json") + |> delete("/api/pleroma/admin/users/permission_group/admin", %{ + nicknames: [user_one.nickname, user_two.nickname] + }) - assert json_response(conn, :forbidden) + assert json_response(conn, 200) == %{"is_admin" => false} + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{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 - registrations_open = Pleroma.Config.get([:instance, :registrations_open]) - invites_enabled = Pleroma.Config.get([:instance, :invites_enabled]) + clear_config([:instance, :registrations_open]) do Pleroma.Config.put([:instance, :registrations_open], false) - Pleroma.Config.put([:instance, :invites_enabled], true) - - on_exit(fn -> - Pleroma.Config.put([:instance, :registrations_open], registrations_open) - Pleroma.Config.put([:instance, :invites_enabled], invites_enabled) - :ok - end) + end - [user: insert(:user, info: %{is_admin: true})] + clear_config([:instance, :invites_enabled]) do + Pleroma.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 @@ -330,7 +607,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do email = Pleroma.Emails.UserEmail.user_invitation_email( - user, + admin, token_record, recipient_email, recipient_name @@ -343,12 +620,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) @@ -356,87 +635,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 - registrations_open = Pleroma.Config.get([:instance, :registrations_open]) - invites_enabled = Pleroma.Config.get([:instance, :invites_enabled]) + test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn} do Pleroma.Config.put([:instance, :registrations_open], false) Pleroma.Config.put([:instance, :invites_enabled], false) - on_exit(fn -> - Pleroma.Config.put([:instance, :registrations_open], registrations_open) - Pleroma.Config.put([:instance, :invites_enabled], invites_enabled) - :ok - end) - - 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 - registrations_open = Pleroma.Config.get([:instance, :registrations_open]) - invites_enabled = Pleroma.Config.get([:instance, :invites_enabled]) + test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do Pleroma.Config.put([:instance, :registrations_open], true) Pleroma.Config.put([:instance, :invites_enabled], true) - on_exit(fn -> - Pleroma.Config.put([:instance, :registrations_open], registrations_open) - Pleroma.Config.put([:instance, :invites_enabled], invites_enabled) - :ok - end) - - 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") @@ -444,24 +678,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"]) @@ -495,14 +731,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 } ] } @@ -519,14 +756,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 } ] } @@ -543,14 +781,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 } ] } @@ -567,14 +806,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 } ] } @@ -591,14 +831,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 } ] } @@ -615,14 +856,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 } ] } @@ -634,21 +876,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) @@ -656,6 +900,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) == %{ @@ -663,51 +908,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, @@ -717,7 +962,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"]) @@ -730,7 +976,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) @@ -746,7 +992,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, @@ -756,7 +1003,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"]) @@ -769,7 +1017,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) @@ -787,7 +1035,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 } ] } @@ -811,7 +1060,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, @@ -821,7 +1071,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"]) @@ -834,15 +1085,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) == %{ @@ -850,58 +1103,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 } - 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) + log_entry = Repo.one(ModerationLog) - {:ok, conn: conn} - end + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} deactivated users: @#{user.nickname}" + 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 @@ -910,12 +1220,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() @@ -924,13 +1234,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 @@ -939,12 +1246,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 @@ -953,16 +1261,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") @@ -991,14 +1289,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, @@ -1010,15 +1304,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "uses" => 0 } end - end - describe "GET /api/pleroma/admin/reports/:id" do - setup %{conn: conn} do - admin = insert(:user, info: %{is_admin: true}) + test "with invalid token", %{conn: conn} do + conn = post(conn, "/api/pleroma/admin/users/revoke_invite", %{"token" => "foo"}) - %{conn: assign(conn, :user, admin)} + assert json_response(conn, :not_found) == "Not found" end + end + describe "GET /api/pleroma/admin/reports/:id" do test "returns report by its id", %{conn: conn} do [reporter, target_user] = insert_pair(:user) activity = insert(:note_activity, user: target_user) @@ -1045,9 +1339,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) @@ -1058,51 +1351,134 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do "status_ids" => [activity.id] }) - %{conn: assign(conn, :user, admin), id: report_id} + {: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} 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 == %{ + "error" => "Insufficient permissions: admin:write:reports." + } - assert response["state"] == "resolved" + conn + |> assign(:token, write_token) + |> patch("/api/pleroma/admin/reports", %{ + "reports" => [%{"state" => "resolved", "id" => id}] + }) + |> json_response(:no_content) end - test "closes report", %{conn: conn, id: id} do - response = - conn - |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "closed"}) - |> json_response(:ok) + 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) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} updated report ##{id} with 'resolved' state" + end + + test "closes report", %{conn: conn, id: id, admin: admin} do + conn + |> patch("/api/pleroma/admin/reports", %{ + "reports" => [ + %{"state" => "closed", "id" => id} + ] + }) + |> json_response(:no_content) + + activity = Activity.get_by_id(id) + assert activity.data["state"] == "closed" - assert response["state"] == "closed" + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} updated report ##{id} with 'closed' state" end 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 @@ -1110,6 +1486,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 @@ -1132,6 +1509,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 @@ -1165,6 +1544,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", %{ @@ -1177,6 +1558,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", %{ @@ -1185,85 +1568,240 @@ 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)} - 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} 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) + + assert length(response["reports"]) == 3 + + first_group = Enum.find(response["reports"], &(&1["status"]["id"] == first_status.id)) + + second_group = Enum.find(response["reports"], &(&1["status"]["id"] == second_status.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 "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") + + response = + conn + |> get("/api/pleroma/admin/grouped_reports") |> json_response(:ok) - recipients = Enum.map(response["mentions"], & &1["username"]) + first_group = Enum.find(response["reports"], &(&1["status"]["id"] == first_status.id)) - assert reporter.nickname in recipients - assert response["content"] == "I will check it out" - assert response["visibility"] == "direct" + assert first_group["status"] == + Map.put( + stringify_keys(StatusView.render("show.json", %{activity: first_status})), + "deleted", + false + ) end - test "returns 400 when status is missing", %{conn: conn} do - conn = post(conn, "/api/pleroma/admin/reports/test/respond") + 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) + + refute Activity.get_by_ap_id(first_status.id) - assert json_response(conn, :bad_request) == "Invalid parameters" + 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 "returns 404 when report id is invalid", %{conn: conn} do - conn = - post(conn, "/api/pleroma/admin/reports/test/respond", %{ - "status" => "foo" - }) + 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) - 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))["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} + %{id: activity.id} end - test "toggle sensitive flag", %{conn: conn, id: id} do + test "toggle sensitive flag", %{conn: conn, id: id, admin: admin} do response = conn |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "true"}) @@ -1271,6 +1809,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do assert response["sensitive"] + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} updated status ##{id}, set sensitive: 'true'" + response = conn |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "false"}) @@ -1279,7 +1822,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do refute response["sensitive"] end - test "change visibility flag", %{conn: conn, id: id} do + test "change visibility flag", %{conn: conn, id: id, admin: admin} do response = conn |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "public"}) @@ -1287,6 +1830,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do assert response["visibility"] == "public" + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} updated status ##{id}, set visibility: 'public'" + response = conn |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "private"}) @@ -1303,65 +1851,76 @@ 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} + %{id: activity.id} end - test "deletes status", %{conn: conn, id: id} do + test "deletes status", %{conn: conn, id: id, admin: admin} do conn |> delete("/api/pleroma/admin/statuses/#{id}") |> json_response(:ok) refute Activity.get_by_id(id) + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{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") + conn = delete(conn, "/api/pleroma/admin/statuses/test") assert json_response(conn, :bad_request) == "Could not delete" end end describe "GET /api/pleroma/admin/config" do - setup %{conn: conn} do - admin = insert(:user, info: %{is_admin: true}) + clear_config(:configurable_from_database) do + Pleroma.Config.put(:configurable_from_database, true) + end - %{conn: assign(conn, :user, admin)} + test "when configuration from database is off", %{conn: conn} do + initial = Pleroma.Config.get(:configurable_from_database) + Pleroma.Config.put(:configurable_from_database, false) + on_exit(fn -> Pleroma.Config.put(:configurable_from_database, initial) end) + conn = get(conn, "/api/pleroma/admin/config") + + assert json_response(conn, 400) == + "To use this endpoint you need to enable configuration from database." end test "without any settings in db", %{conn: conn} do conn = get(conn, "/api/pleroma/admin/config") - assert json_response(conn, 200) == %{"configs" => []} + assert json_response(conn, 400) == + "To use configuration from database migrate your settings to 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" => _ } @@ -1371,13 +1930,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" => []}) + + assert json_response(conn, 400) == + "To use this endpoint you need to enable configuration from database." + end - temp_file = "config/test.exported_from_db.secret.exs" + describe "POST /api/pleroma/admin/config" do + setup do + http = Application.get_env(:pleroma, :http) on_exit(fn -> Application.delete_env(:pleroma, :key1) @@ -1388,33 +2041,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) - end) - - dynamic = Pleroma.Config.get([:instance, :dynamic_configuration]) - - Pleroma.Config.put([:instance, :dynamic_configuration], true) - - on_exit(fn -> - Pleroma.Config.put([:instance, :dynamic_configuration], dynamic) + Application.put_env(:pleroma, :http, http) + Application.put_env(:tesla, :adapter, Tesla.Mock) + :ok = File.rm("config/test.exported_from_db.secret.exs") end) + end - %{conn: assign(conn, :user, admin)} + clear_config(:configurable_from_database) do + Pleroma.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" => [ @@ -1424,21 +2077,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", []]} } ] @@ -1447,43 +2100,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"] } ] } @@ -1511,25 +2170,158 @@ 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 config setting without 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: 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: ":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 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"]]}] + ] + } + ] } ] }) @@ -1537,47 +2329,239 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do assert json_response(conn, 200) == %{ "configs" => [ %{ - "group" => "pleroma", + "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: config.group, + key: config.key, + value: [":console", %{"tuple" => ["ExSyslogger", ":ex_syslogger"]}] + } + ] + }) + + assert json_response(conn, 200) == %{ + "configs" => [ + %{ + "group" => ":logger", + "key" => ":backends", + "value" => [ + ":console", + %{"tuple" => ["ExSyslogger", ":ex_syslogger"]} + ], + "db" => [":backends"] + } + ] + } + + assert Application.get_env(:logger, :backends) == [ + :console, + {ExSyslogger, :ex_syslogger} + ] + + ExUnit.CaptureLog.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]}, %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]}, %{"tuple" => [":seconds_valid", 60]}, %{"tuple" => [":path", ""]}, - %{"tuple" => [":key1", nil]} + %{"tuple" => [":key1", nil]}, + %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}, + %{"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" => [":name", "Pleroma"]} ] + }, + %{ + "group" => ":tesla", + "key" => ":adapter", + "value" => "Tesla.Adapter.Httpc" } ] }) + assert Application.get_env(:tesla, :adapter) == Tesla.Adapter.Httpc + assert Pleroma.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]}, %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]}, %{"tuple" => [":seconds_valid", 60]}, %{"tuple" => [":path", ""]}, - %{"tuple" => [":key1", nil]} + %{"tuple" => [":key1", nil]}, + %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}, + %{"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" => [":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"] } ] } @@ -1588,7 +2572,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do post(conn, "/api/pleroma/admin/config", %{ configs: [ %{ - "group" => "pleroma", + "group" => ":pleroma", "key" => "Pleroma.Web.Endpoint.NotReal", "value" => [ %{ @@ -1652,7 +2636,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do assert json_response(conn, 200) == %{ "configs" => [ %{ - "group" => "pleroma", + "group" => ":pleroma", "key" => "Pleroma.Web.Endpoint.NotReal", "value" => [ %{ @@ -1708,7 +2692,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do ] ] } - ] + ], + "db" => [":http"] } ] } @@ -1719,7 +2704,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"]}, @@ -1749,7 +2734,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do %{ "configs" => [ %{ - "group" => "pleroma", + "group" => ":pleroma", "key" => ":key1", "value" => [ %{"tuple" => [":key2", "some_val"]}, @@ -1770,7 +2755,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do } ] } - ] + ], + "db" => [":key2", ":key3"] } ] } @@ -1781,7 +2767,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do post(conn, "/api/pleroma/admin/config", %{ configs: [ %{ - "group" => "pleroma", + "group" => ":pleroma", "key" => ":key1", "value" => %{"key" => "some_val"} } @@ -1792,92 +2778,104 @@ 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 + test "queues key as atom", %{conn: conn} do conn = post(conn, "/api/pleroma/admin/config", %{ configs: [ %{ - "group" => "pleroma", - "key" => "Pleroma.Web.Endpoint.NotReal", + "group" => ":oban", + "key" => ":queues", "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, []}} - ]}"]]} - ] - ] - } + %{"tuple" => [":federator_incoming", 50]}, + %{"tuple" => [":federator_outgoing", 50]}, + %{"tuple" => [":web_push", 50]}, + %{"tuple" => [":mailer", 10]}, + %{"tuple" => [":transmogrifier", 20]}, + %{"tuple" => [":scheduled_activities", 10]}, + %{"tuple" => [":background", 5]} ] } ] }) - 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", + "group" => ":oban", + "key" => ":queues", "value" => [ - %{ - "tuple" => [ - ":http", - [ - %{"tuple" => [":ip", %{"tuple" => [127, 0, 0, 1]}]}, - %{ - "tuple" => [ - ":dispatch", - [ - dispatch_string - ] - ] - } - ] - ] - } + %{"tuple" => [":federator_incoming", 50]}, + %{"tuple" => [":federator_outgoing", 50]}, + %{"tuple" => [":web_push", 50]}, + %{"tuple" => [":mailer", 10]}, + %{"tuple" => [":transmogrifier", 20]}, + %{"tuple" => [":scheduled_activities", 10]}, + %{"tuple" => [":background", 5]} + ], + "db" => [ + ":federator_incoming", + ":federator_outgoing", + ":web_push", + ":mailer", + ":transmogrifier", + ":scheduled_activities", + ":background" ] } ] } end - test "queues key as atom", %{conn: conn} do + test "delete part of settings by atom subkeys", %{conn: conn} do + config = + insert(:config, + key: ":keyaa1", + value: :erlang.term_to_binary(subkey1: "val1", subkey2: "val2", subkey3: "val3") + ) + conn = post(conn, "/api/pleroma/admin/config", %{ configs: [ %{ - "group" => "pleroma_job_queue", - "key" => ":queues", - "value" => [ - %{"tuple" => [":federator_incoming", 50]}, - %{"tuple" => [":federator_outgoing", 50]}, - %{"tuple" => [":web_push", 50]}, - %{"tuple" => [":mailer", 10]}, - %{"tuple" => [":transmogrifier", 20]}, - %{"tuple" => [":scheduled_activities", 10]}, - %{"tuple" => [":background", 5]} + group: config.group, + key: config.key, + subkeys: [":subkey1", ":subkey3"], + delete: true + } + ] + }) + + 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: ":http", + value: [ + %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]}, + %{"tuple" => [":send_user_agent", false]} ] } ] @@ -1886,21 +2884,602 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do assert json_response(conn, 200) == %{ "configs" => [ %{ - "group" => "pleroma_job_queue", - "key" => ":queues", + "group" => ":pleroma", + "key" => ":http", "value" => [ - %{"tuple" => [":federator_incoming", 50]}, - %{"tuple" => [":federator_outgoing", 50]}, - %{"tuple" => [":web_push", 50]}, - %{"tuple" => [":mailer", 10]}, - %{"tuple" => [":transmogrifier", 20]}, - %{"tuple" => [":scheduled_activities", 10]}, - %{"tuple" => [":background", 5]} - ] + %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]}, + %{"tuple" => [":send_user_agent", false]} + ], + "db" => [":proxy_url", ":send_user_agent"] } ] } end + + 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]} + ] + } + ] + }) + + 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 + + describe "config mix tasks run" do + setup do + Mix.shell(Mix.Shell.Quiet) + + on_exit(fn -> + Mix.shell(Mix.Shell.IO) + end) + + :ok + end + + clear_config(:configurable_from_database) do + Pleroma.Config.put(:configurable_from_database, true) + end + + clear_config([:feed, :post_title]) do + Pleroma.Config.put([:feed, :post_title], %{max_length: 100, omission: "…"}) + end + + test "transfer settings to DB and to file", %{conn: conn} do + assert Repo.all(Pleroma.ConfigDB) == [] + Mix.Tasks.Pleroma.Config.migrate_to_db("test/fixtures/config/temp.secret.exs") + assert Repo.aggregate(Pleroma.ConfigDB, :count, :id) > 0 + + conn = get(conn, "/api/pleroma/admin/config/migrate_from_db") + + assert json_response(conn, 200) == %{} + assert Repo.all(Pleroma.ConfigDB) == [] + end + + test "returns error if configuration from database is off", %{conn: conn} do + initial = Pleroma.Config.get(:configurable_from_database) + on_exit(fn -> Pleroma.Config.put(:configurable_from_database, initial) end) + Pleroma.Config.put(:configurable_from_database, false) + + conn = get(conn, "/api/pleroma/admin/config/migrate_from_db") + + assert json_response(conn, 400) == + "To use this endpoint you need to enable configuration from database." + + assert Repo.all(Pleroma.ConfigDB) == [] + end + end + + describe "GET /api/pleroma/admin/users/:nickname/statuses" do + setup do + user = insert(:user) + + date1 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!() + date2 = (DateTime.to_unix(DateTime.utc_now()) + 1000) |> DateTime.from_unix!() + date3 = (DateTime.to_unix(DateTime.utc_now()) + 3000) |> DateTime.from_unix!() + + insert(:note_activity, user: user, published: date1) + insert(:note_activity, user: user, published: date2) + insert(:note_activity, user: user, published: date3) + + %{user: user} + end + + test "renders user's statuses", %{conn: conn, user: user} do + conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses") + + assert json_response(conn, 200) |> length() == 3 + end + + test "renders user's statuses with a limit", %{conn: conn, user: user} do + conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=2") + + assert json_response(conn, 200) |> length() == 2 + end + + test "doesn't return private statuses by default", %{conn: conn, user: user} do + {:ok, _private_status} = + CommonAPI.post(user, %{"status" => "private", "visibility" => "private"}) + + {:ok, _public_status} = + CommonAPI.post(user, %{"status" => "public", "visibility" => "public"}) + + conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses") + + assert json_response(conn, 200) |> length() == 4 + end + + test "returns private statuses with godmode on", %{conn: conn, user: user} do + {:ok, _private_status} = + CommonAPI.post(user, %{"status" => "private", "visibility" => "private"}) + + {:ok, _public_status} = + CommonAPI.post(user, %{"status" => "public", "visibility" => "public"}) + + conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?godmode=true") + + assert json_response(conn, 200) |> length() == 5 + end + end + + describe "GET /api/pleroma/admin/moderation_log" do + setup do + moderator = insert(:user, is_moderator: true) + + %{moderator: moderator} + end + + test "returns the log", %{conn: conn, admin: admin} do + Repo.insert(%ModerationLog{ + data: %{ + actor: %{ + "id" => admin.id, + "nickname" => admin.nickname, + "type" => "user" + }, + action: "relay_follow", + target: "https://example.org/relay" + }, + inserted_at: NaiveDateTime.truncate(~N[2017-08-15 15:47:06.597036], :second) + }) + + Repo.insert(%ModerationLog{ + data: %{ + actor: %{ + "id" => admin.id, + "nickname" => admin.nickname, + "type" => "user" + }, + action: "relay_unfollow", + target: "https://example.org/relay" + }, + inserted_at: NaiveDateTime.truncate(~N[2017-08-16 15:47:06.597036], :second) + }) + + conn = get(conn, "/api/pleroma/admin/moderation_log") + + response = json_response(conn, 200) + [first_entry, second_entry] = response["items"] + + assert response["total"] == 2 + assert first_entry["data"]["action"] == "relay_unfollow" + + assert first_entry["message"] == + "@#{admin.nickname} unfollowed relay: https://example.org/relay" + + assert second_entry["data"]["action"] == "relay_follow" + + assert second_entry["message"] == + "@#{admin.nickname} followed relay: https://example.org/relay" + end + + test "returns the log with pagination", %{conn: conn, admin: admin} do + Repo.insert(%ModerationLog{ + data: %{ + actor: %{ + "id" => admin.id, + "nickname" => admin.nickname, + "type" => "user" + }, + action: "relay_follow", + target: "https://example.org/relay" + }, + inserted_at: NaiveDateTime.truncate(~N[2017-08-15 15:47:06.597036], :second) + }) + + Repo.insert(%ModerationLog{ + data: %{ + actor: %{ + "id" => admin.id, + "nickname" => admin.nickname, + "type" => "user" + }, + action: "relay_unfollow", + target: "https://example.org/relay" + }, + inserted_at: NaiveDateTime.truncate(~N[2017-08-16 15:47:06.597036], :second) + }) + + conn1 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=1") + + response1 = json_response(conn1, 200) + [first_entry] = response1["items"] + + assert response1["total"] == 2 + assert response1["items"] |> length() == 1 + assert first_entry["data"]["action"] == "relay_unfollow" + + assert first_entry["message"] == + "@#{admin.nickname} unfollowed relay: https://example.org/relay" + + conn2 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=2") + + response2 = json_response(conn2, 200) + [second_entry] = response2["items"] + + 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) + 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) + 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 end diff --git a/test/web/admin_api/config_test.exs b/test/web/admin_api/config_test.exs deleted file mode 100644 index d41666ef3..000000000 --- a/test/web/admin_api/config_test.exs +++ /dev/null @@ -1,465 +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 "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" 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..082e691c4 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-2019 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..a0c6eab3c 100644 --- a/test/web/admin_api/views/report_view_test.exs +++ b/test/web/admin_api/views/report_view_test.exs @@ -5,6 +5,7 @@ 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/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 16b3f121d..f8963e42e 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -5,18 +5,69 @@ defmodule Pleroma.Web.CommonAPITest do use Pleroma.DataCase alias Pleroma.Activity + alias Pleroma.Conversation.Participation alias Pleroma.Object 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 + require Pleroma.Constants + + clear_config([:instance, :safe_dm_mentions]) + clear_config([:instance, :limit]) + clear_config([:instance, :max_pinned_statuses]) + + test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) + + [participation] = Participation.for_user(user) + + {:ok, convo_reply} = + CommonAPI.post(user, %{"status" => ".", "in_reply_to_conversation_id" => participation.id}) + + assert Visibility.is_direct?(convo_reply) + + assert activity.data["context"] == convo_reply.data["context"] + end + + test "when replying to a conversation / participation, it only mentions the recipients explicitly declared in the participation" do + har = insert(:user) + jafnhar = insert(:user) + tridi = insert(:user) + + {:ok, activity} = + CommonAPI.post(har, %{ + "status" => "@#{jafnhar.nickname} hey", + "visibility" => "direct" + }) + + assert har.ap_id in activity.recipients + assert jafnhar.ap_id in activity.recipients + + [participation] = Participation.for_user(har) + + {:ok, activity} = + CommonAPI.post(har, %{ + "status" => "I don't really like @#{tridi.nickname}", + "visibility" => "direct", + "in_reply_to_status_id" => activity.id, + "in_reply_to_conversation_id" => participation.id + }) + + assert har.ap_id in activity.recipients + assert jafnhar.ap_id in activity.recipients + refute tridi.ap_id in activity.recipients + end + test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do har = insert(:user) jafnhar = insert(:user) tridi = insert(:user) - option = Pleroma.Config.get([:instance, :safe_dm_mentions]) Pleroma.Config.put([:instance, :safe_dm_mentions], true) {:ok, activity} = @@ -27,7 +78,6 @@ defmodule Pleroma.Web.CommonAPITest do refute tridi.ap_id in activity.recipients assert jafnhar.ap_id in activity.recipients - Pleroma.Config.put([:instance, :safe_dm_mentions], option) end test "it de-duplicates tags" do @@ -49,11 +99,13 @@ defmodule Pleroma.Web.CommonAPITest do test "it adds emoji when updating profiles" do user = insert(:user, %{name: ":firefox:"}) - CommonAPI.update(user) + {: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:" + + assert Pleroma.Constants.as_public() in activity.recipients end describe "posting" do @@ -89,7 +141,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 @@ -105,7 +157,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 @@ -150,19 +202,58 @@ defmodule Pleroma.Web.CommonAPITest do end test "it returns error when character limit is exceeded" do - limit = Pleroma.Config.get([:instance, :limit]) Pleroma.Config.put([:instance, :limit], 5) user = insert(:user) assert {:error, "The status is over the character limit"} = CommonAPI.post(user, %{"status" => "foobar"}) + end + + test "it can handle activities that expire" do + user = insert(:user) - Pleroma.Config.put([:instance, :limit], limit) + expires_at = + NaiveDateTime.utc_now() + |> NaiveDateTime.truncate(:second) + |> NaiveDateTime.add(1_000_000, :second) + + assert {:ok, activity} = + CommonAPI.post(user, %{"status" => "chai", "expires_in" => 1_000_000}) + + assert expiration = Pleroma.ActivityExpiration.get_by_activity_id(activity.id) + assert expiration.scheduled_at == expires_at end 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"] == "👍" + + # TODO: test error case. + 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) @@ -172,6 +263,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) @@ -181,22 +284,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 @@ -216,7 +319,7 @@ 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 "unlisted statuses can be pinned", %{user: user} do @@ -250,7 +353,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 @@ -262,7 +365,7 @@ defmodule Pleroma.Web.CommonAPITest do user = refresh_record(user) - assert %User{info: %{pinned_activities: []}} = user + assert %User{pinned_activities: []} = user end end @@ -310,6 +413,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{ @@ -317,7 +428,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 @@ -337,6 +448,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 @@ -352,6 +468,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 @@ -364,14 +509,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 @@ -381,7 +526,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) @@ -393,7 +538,7 @@ defmodule Pleroma.Web.CommonAPITest do 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) @@ -413,7 +558,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) @@ -451,4 +596,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 af320f31f..4b761e039 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-2019 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") @@ -239,7 +239,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do mentioned_user = insert(:user) mentions = [mentioned_user.ap_id] - {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "public") + {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "public", nil) assert length(to) == 2 assert length(cc) == 1 @@ -256,7 +256,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do {:ok, activity} = CommonAPI.post(third_user, %{"status" => "uguu"}) mentions = [mentioned_user.ap_id] - {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "public") + {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "public", nil) assert length(to) == 3 assert length(cc) == 1 @@ -272,7 +272,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do mentioned_user = insert(:user) mentions = [mentioned_user.ap_id] - {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "unlisted") + {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "unlisted", nil) assert length(to) == 2 assert length(cc) == 1 @@ -289,7 +289,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do {:ok, activity} = CommonAPI.post(third_user, %{"status" => "uguu"}) mentions = [mentioned_user.ap_id] - {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "unlisted") + {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "unlisted", nil) assert length(to) == 3 assert length(cc) == 1 @@ -305,10 +305,9 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do mentioned_user = insert(:user) mentions = [mentioned_user.ap_id] - {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "private") - + {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 @@ -321,10 +320,10 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do {:ok, activity} = CommonAPI.post(third_user, %{"status" => "uguu"}) mentions = [mentioned_user.ap_id] - {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "private") + {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 @@ -336,10 +335,10 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do mentioned_user = insert(:user) mentions = [mentioned_user.ap_id] - {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "direct") + {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 @@ -351,13 +350,251 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do {:ok, activity} = CommonAPI.post(third_user, %{"status" => "uguu"}) mentions = [mentioned_user.ap_id] - {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "direct") + {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 end end + + describe "get_by_id_or_ap_id/1" do + test "get activity by id" do + activity = insert(:note_activity) + %Pleroma.Activity{} = note = Utils.get_by_id_or_ap_id(activity.id) + assert note.id == activity.id + end + + test "get activity by ap_id" do + activity = insert(:note_activity) + %Pleroma.Activity{} = note = Utils.get_by_id_or_ap_id(activity.data["object"]) + assert note.id == activity.id + end + + test "get activity by object when type isn't `Create` " do + activity = insert(:like_activity) + %Pleroma.Activity{} = like = Utils.get_by_id_or_ap_id(activity.id) + assert like.data["object"] == activity.data["object"] + end + end + + describe "to_master_date/1" do + test "removes microseconds from date (NaiveDateTime)" do + assert Utils.to_masto_date(~N[2015-01-23 23:50:07.123]) == "2015-01-23T23:50:07.000Z" + end + + test "removes microseconds from date (String)" do + assert Utils.to_masto_date("2015-01-23T23:50:07.123Z") == "2015-01-23T23:50:07.000Z" + end + + test "returns empty string when date invalid" do + assert Utils.to_masto_date("2015-01?23T23:50:07.123Z") == "" + end + end + + describe "conversation_id_to_context/1" do + test "returns id" do + object = insert(:note) + assert Utils.conversation_id_to_context(object.id) == object.data["id"] + end + + test "returns error if object not found" do + assert Utils.conversation_id_to_context("123") == {:error, "No such conversation"} + end + end + + describe "maybe_notify_mentioned_recipients/2" do + test "returns recipients when activity is not `Create`" do + activity = insert(:like_activity) + assert Utils.maybe_notify_mentioned_recipients(["test"], activity) == ["test"] + end + + test "returns recipients from tag" do + user = insert(:user) + + object = + insert(:note, + user: user, + data: %{ + "tag" => [ + %{"type" => "Hashtag"}, + "", + %{"type" => "Mention", "href" => "https://testing.pleroma.lol/users/lain"}, + %{"type" => "Mention", "href" => "https://shitposter.club/user/5381"}, + %{"type" => "Mention", "href" => "https://shitposter.club/user/5381"} + ] + } + ) + + activity = insert(:note_activity, user: user, note: object) + + assert Utils.maybe_notify_mentioned_recipients(["test"], activity) == [ + "test", + "https://testing.pleroma.lol/users/lain", + "https://shitposter.club/user/5381" + ] + end + + test "returns recipients when object is map" do + user = insert(:user) + object = insert(:note, user: user) + + activity = + insert(:note_activity, + user: user, + note: object, + data_attrs: %{ + "object" => %{ + "tag" => [ + %{"type" => "Hashtag"}, + "", + %{"type" => "Mention", "href" => "https://testing.pleroma.lol/users/lain"}, + %{"type" => "Mention", "href" => "https://shitposter.club/user/5381"}, + %{"type" => "Mention", "href" => "https://shitposter.club/user/5381"} + ] + } + } + ) + + Pleroma.Repo.delete(object) + + assert Utils.maybe_notify_mentioned_recipients(["test"], activity) == [ + "test", + "https://testing.pleroma.lol/users/lain", + "https://shitposter.club/user/5381" + ] + end + + test "returns recipients when object not found" do + user = insert(:user) + object = insert(:note, user: user) + + activity = insert(:note_activity, user: user, note: object) + Pleroma.Repo.delete(object) + + assert Utils.maybe_notify_mentioned_recipients(["test-test"], activity) == [ + "test-test" + ] + end + end + + describe "attachments_from_ids_descs/2" do + test "returns [] when attachment ids is empty" do + assert Utils.attachments_from_ids_descs([], "{}") == [] + end + + test "returns list attachments with desc" do + object = insert(:note) + desc = Jason.encode!(%{object.id => "test-desc"}) + + assert Utils.attachments_from_ids_descs(["#{object.id}", "34"], desc) == [ + Map.merge(object.data, %{"name" => "test-desc"}) + ] + end + end + + describe "attachments_from_ids/1" do + test "returns attachments with descs" do + object = insert(:note) + desc = Jason.encode!(%{object.id => "test-desc"}) + + assert Utils.attachments_from_ids(%{ + "media_ids" => ["#{object.id}"], + "descriptions" => desc + }) == [ + Map.merge(object.data, %{"name" => "test-desc"}) + ] + end + + test "returns attachments without descs" do + object = insert(:note) + assert Utils.attachments_from_ids(%{"media_ids" => ["#{object.id}"]}) == [object.data] + end + + test "returns [] when not pass media_ids" do + assert Utils.attachments_from_ids(%{}) == [] + end + end + + describe "maybe_add_list_data/3" do + test "adds list params when found user list" do + user = insert(:user) + {:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", user) + + assert Utils.maybe_add_list_data(%{additional: %{}, object: %{}}, user, {:list, list.id}) == + %{ + additional: %{"bcc" => [list.ap_id], "listMessage" => list.ap_id}, + object: %{"listMessage" => list.ap_id} + } + end + + test "returns original params when list not found" do + user = insert(:user) + {:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", insert(:user)) + + assert Utils.maybe_add_list_data(%{additional: %{}, object: %{}}, user, {:list, list.id}) == + %{additional: %{}, object: %{}} + end + end + + describe "make_note_data/11" do + test "returns note data" do + user = insert(:user) + note = insert(:note) + user2 = insert(:user) + user3 = insert(:user) + + assert Utils.make_note_data( + user.ap_id, + [user2.ap_id], + "2hu", + "<h1>This is :moominmamma: note</h1>", + [], + note.id, + [name: "jimm"], + "test summary", + [user3.ap_id], + false, + %{"custom_tag" => "test"} + ) == %{ + "actor" => user.ap_id, + "attachment" => [], + "cc" => [user3.ap_id], + "content" => "<h1>This is :moominmamma: note</h1>", + "context" => "2hu", + "sensitive" => false, + "summary" => "test summary", + "tag" => ["jimm"], + "to" => [user2.ap_id], + "type" => "Note", + "custom_tag" => "test" + } + end + end + + describe "maybe_add_attachments/3" do + test "returns parsed results when no_links is true" do + assert Utils.maybe_add_attachments( + {"test", [], ["tags"]}, + [], + true + ) == {"test", [], ["tags"]} + end + + test "adds attachments to parsed results" do + attachment = %{"url" => [%{"href" => "SakuraPM.png"}]} + + assert Utils.maybe_add_attachments( + {"test", [], ["tags"]}, + [attachment], + false + ) == { + "test<br><a href=\"SakuraPM.png\" class='attachment'>SakuraPM.png</a>", + [], + ["tags"] + } + end + end end diff --git a/test/web/fallback_test.exs b/test/web/fallback_test.exs index cc78b3ae1..c13db9526 100644 --- a/test/web/fallback_test.exs +++ b/test/web/fallback_test.exs @@ -30,6 +30,10 @@ defmodule Pleroma.Web.FallbackTest do |> json_response(404) == %{"error" => "Not implemented"} end + test "GET /pleroma/admin -> /pleroma/admin/", %{conn: conn} do + assert redirected_to(get(conn, "/pleroma/admin")) =~ "/pleroma/admin/" + end + test "GET /*path", %{conn: conn} do assert conn |> get("/foo") diff --git a/test/web/federator_test.exs b/test/web/federator_test.exs index 69dd4d747..c224197c3 100644 --- a/test/web/federator_test.exs +++ b/test/web/federator_test.exs @@ -1,27 +1,34 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 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 setup_all do Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) - config_path = [:instance, :federating] - initial_setting = Pleroma.Config.get(config_path) - - Pleroma.Config.put(config_path, true) - on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end) - :ok end + clear_config_all([:instance, :federating]) do + Pleroma.Config.put([:instance, :federating], true) + end + + clear_config([:instance, :allow_relay]) + clear_config([:instance, :rewrite_policy]) + clear_config([:mrf_keyword]) + describe "Publish an activity" do setup do user = insert(:user) @@ -42,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 @@ -55,19 +63,15 @@ defmodule Pleroma.Web.FederatorTest do with_mocks([relay_mock]) do Federator.publish(activity) + ObanHelpers.perform(all_enqueued(worker: PublisherWorker)) end refute_received :relay_publish - - Pleroma.Config.put([:instance, :allow_relay], true) end 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} = @@ -77,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() @@ -95,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 + expected_dt = NaiveDateTime.to_iso8601(dt) - 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" - }) - - 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 @@ -200,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 @@ -218,7 +150,24 @@ 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 + Pleroma.Config.put([:mrf_keyword, :reject], ["lain"]) + + Pleroma.Config.put( + [:instance, :rewrite_policy], + Pleroma.Web.ActivityPub.MRF.KeywordPolicy + ) + + params = + File.read!("test/fixtures/mastodon-post-activity.json") + |> Poison.decode!() + + assert {:ok, job} = Federator.incoming_ap_doc(params) + assert :error = ObanHelpers.perform(job) end end end diff --git a/test/web/feed/feed_controller_test.exs b/test/web/feed/feed_controller_test.exs new file mode 100644 index 000000000..6f61acf43 --- /dev/null +++ b/test/web/feed/feed_controller_test.exs @@ -0,0 +1,251 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Feed.FeedControllerTest 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("/users/#{user.nickname}/feed.atom") + |> 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("/users/nonexisting/feed.atom") + + 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("/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", + "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 d28730994..e54d708ad 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-2019 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Instances.InstanceTest do @@ -10,19 +10,14 @@ defmodule Pleroma.Instances.InstanceTest do import Pleroma.Factory - setup_all do - config_path = [:instance, :federation_reachability_timeout_days] - initial_setting = Pleroma.Config.get(config_path) - - Pleroma.Config.put(config_path, 1) - on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end) - - :ok + clear_config_all([:instance, :federation_reachability_timeout_days]) do + Pleroma.Config.put([:instance, :federation_reachability_timeout_days], 1) end 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 f0d84edea..65b03b155 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-2019 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.InstancesTest do @@ -7,14 +7,8 @@ defmodule Pleroma.InstancesTest do use Pleroma.DataCase - setup_all do - config_path = [:instance, :federation_reachability_timeout_days] - initial_setting = Pleroma.Config.get(config_path) - - Pleroma.Config.put(config_path, 1) - on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end) - - :ok + clear_config_all([:instance, :federation_reachability_timeout_days]) do + Pleroma.Config.put([:instance, :federation_reachability_timeout_days], 1) end describe "reachable?/1" 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..f9870a852 --- /dev/null +++ b/test/web/masto_fe_controller_test.exs @@ -0,0 +1,85 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 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/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs new file mode 100644 index 000000000..09bdc46e0 --- /dev/null +++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs @@ -0,0 +1,360 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do + alias Pleroma.Repo + alias Pleroma.User + + use Pleroma.Web.ConnCase + + import Pleroma.Factory + clear_config([:instance, :max_account_fields]) + + describe "updating credentials" do + setup do: oauth_access(["write:accounts"]) + + test "sets user settings in a generic way", %{conn: conn} do + res_conn = + patch(conn, "/api/v1/accounts/update_credentials", %{ + "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_data["id"]) + + res_conn = + conn + |> assign(:user, user) + |> patch("/api/v1/accounts/update_credentials", %{ + "pleroma_settings_store" => %{ + masto_fe: %{ + theme: "bla" + } + } + }) + + assert user_data = json_response(res_conn, 200) + + assert user_data["pleroma"]["settings_store"] == + %{ + "pleroma_fe" => %{"theme" => "bla"}, + "masto_fe" => %{"theme" => "bla"} + } + + user = Repo.get(User, user_data["id"]) + + res_conn = + conn + |> assign(:user, user) + |> patch("/api/v1/accounts/update_credentials", %{ + "pleroma_settings_store" => %{ + masto_fe: %{ + theme: "blub" + } + } + }) + + assert user_data = json_response(res_conn, 200) + + assert user_data["pleroma"]["settings_store"] == + %{ + "pleroma_fe" => %{"theme" => "bla"}, + "masto_fe" => %{"theme" => "blub"} + } + end + + test "updates the user's bio", %{conn: conn} do + user2 = insert(:user) + + conn = + patch(conn, "/api/v1/accounts/update_credentials", %{ + "note" => "I drink #cofe with @#{user2.nickname}" + }) + + assert user_data = json_response(conn, 200) + + 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 + conn = patch(conn, "/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 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 + conn = patch(conn, "/api/v1/accounts/update_credentials", %{default_scope: "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 + 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 = + patch(conn, "/api/v1/accounts/update_credentials", %{ + hide_followers_count: "true", + hide_follows_count: "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", %{user: user, conn: conn} do + response = + conn + |> patch("/api/v1/accounts/update_credentials", %{skip_thread_containment: "true"}) + |> json_response(200) + + assert response["pleroma"]["skip_thread_containment"] == true + assert refresh_record(user).skip_thread_containment + end + + test "updates the user's hide_follows status", %{conn: conn} do + conn = patch(conn, "/api/v1/accounts/update_credentials", %{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 + conn = patch(conn, "/api/v1/accounts/update_credentials", %{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 + conn = patch(conn, "/api/v1/accounts/update_credentials", %{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 + conn = patch(conn, "/api/v1/accounts/update_credentials", %{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 + conn = + patch(conn, "/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"}) + + assert user_data = json_response(conn, 200) + assert user_data["display_name"] == "markorepairs" + end + + 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 = 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", %{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 = 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 + new_header = %Plug.Upload{ + content_type: "image/jpg", + path: Path.absname("test/fixtures/image.jpg"), + filename: "an_image.jpg" + } + + conn = + patch(conn, "/api/v1/accounts/update_credentials", %{ + "pleroma_background_image" => new_header + }) + + assert user_response = json_response(conn, 200) + assert user_response["pleroma"]["background_image"] + end + + 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 = + build_conn() + |> put_req_header("authorization", "Bearer #{token.token}") + |> patch("/api/v1/accounts/update_credentials", %{}) + + if token == token1 do + assert %{"error" => "Insufficient permissions: write:accounts."} == + json_response(conn, 403) + else + assert json_response(conn, 200) + end + end + end + + test "updates profile emojos", %{user: user, conn: conn} do + note = "*sips :blank:*" + name = "I am :firefox:" + + ret_conn = + patch(conn, "/api/v1/accounts/update_credentials", %{ + "note" => note, + "display_name" => name + }) + + assert json_response(ret_conn, 200) + + conn = get(conn, "/api/v1/accounts/#{user.id}") + + assert user_data = json_response(conn, 200) + + 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 + fields = [ + %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "<script>bar</script>"}, + %{"name" => "link", "value" => "cofe.io"} + ] + + account_data = + conn + |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) + |> json_response(200) + + assert account_data["fields"] == [ + %{"name" => "foo", "value" => "bar"}, + %{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)} + ] + + assert account_data["source"]["fields"] == [ + %{ + "name" => "<a href=\"http://google.com\">foo</a>", + "value" => "<script>bar</script>" + }, + %{"name" => "link", "value" => "cofe.io"} + ] + + fields = + [ + "fields_attributes[1][name]=link", + "fields_attributes[1][value]=cofe.io", + "fields_attributes[0][name]=<a href=\"http://google.com\">foo</a>", + "fields_attributes[0][value]=bar" + ] + |> Enum.join("&") + + account = + conn + |> put_req_header("content-type", "application/x-www-form-urlencoded") + |> patch("/api/v1/accounts/update_credentials", fields) + |> json_response(200) + + assert account["fields"] == [ + %{"name" => "foo", "value" => "bar"}, + %{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)} + ] + + assert account["source"]["fields"] == [ + %{ + "name" => "<a href=\"http://google.com\">foo</a>", + "value" => "bar" + }, + %{"name" => "link", "value" => "cofe.io"} + ] + + name_limit = Pleroma.Config.get([:instance, :account_field_name_length]) + value_limit = Pleroma.Config.get([:instance, :account_field_value_length]) + + long_value = Enum.map(0..value_limit, fn _ -> "x" end) |> Enum.join() + + fields = [%{"name" => "<b>foo<b>", "value" => long_value}] + + assert %{"error" => "Invalid request"} == + conn + |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) + |> json_response(403) + + long_name = Enum.map(0..name_limit, fn _ -> "x" end) |> Enum.join() + + fields = [%{"name" => long_name, "value" => "bar"}] + + assert %{"error" => "Invalid request"} == + conn + |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) + |> json_response(403) + + Pleroma.Config.put([:instance, :max_account_fields], 1) + + fields = [ + %{"name" => "<b>foo<b>", "value" => "<i>bar</i>"}, + %{"name" => "link", "value" => "cofe.io"} + ] + + assert %{"error" => "Invalid request"} == + conn + |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) + |> json_response(403) + + fields = [ + %{"name" => "foo", "value" => ""}, + %{"name" => "", "value" => "bar"} + ] + + account = + conn + |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) + |> json_response(200) + + assert account["fields"] == [ + %{"name" => "foo", "value" => ""} + ] + end + end +end 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..0d4860a42 --- /dev/null +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -0,0 +1,841 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 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 + 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) + |> assign(:token, insert(:oauth_token, user: reading_user, scopes: ["read:accounts"])) + |> 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 "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 "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 "rate limit", %{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..5 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 + + 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 "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..51788155b --- /dev/null +++ b/test/web/mastodon_api/controllers/app_controller_test.exs @@ -0,0 +1,60 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 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..98b2a82e7 --- /dev/null +++ b/test/web/mastodon_api/controllers/auth_controller_test.exs @@ -0,0 +1,121 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 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 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..4bb9781a6 --- /dev/null +++ b/test/web/mastodon_api/controllers/conversation_controller_test.exs @@ -0,0 +1,208 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 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..2d988b0b8 --- /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-2019 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..55de625ba --- /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-2019 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..3aea17ec7 --- /dev/null +++ b/test/web/mastodon_api/controllers/filter_controller_test.exs @@ -0,0 +1,123 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 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..6e4a76501 --- /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-2019 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..e00de6b18 --- /dev/null +++ b/test/web/mastodon_api/controllers/instance_controller_test.exs @@ -0,0 +1,77 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 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 new file mode 100644 index 000000000..a6effbb69 --- /dev/null +++ b/test/web/mastodon_api/controllers/list_controller_test.exs @@ -0,0 +1,142 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.ListControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Repo + + import Pleroma.Factory + + test "creating a list" do + %{conn: conn} = oauth_access(["write:lists"]) + + 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" do + %{conn: conn} = oauth_access(["write:lists"]) + + 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" do + %{conn: conn} = oauth_access(["read:lists", "write:lists"]) + + conn + |> post("/api/v1/lists", %{"title" => "cuties"}) + |> json_response(:ok) + + conn + |> post("/api/v1/lists", %{"title" => "cofe"}) + |> json_response(:ok) + + conn = get(conn, "/api/v1/lists") + + assert [ + %{"id" => _, "title" => "cofe"}, + %{"id" => _, "title" => "cuties"} + ] = json_response(conn, :ok) + end + + 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 = 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" 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 = 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" 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) + + conn = + conn + |> assign(:user, user) + |> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) + + assert [%{"id" => id}] = json_response(conn, 200) + assert id == to_string(other_user.id) + end + + test "retrieving a list" do + %{user: user, conn: conn} = oauth_access(["read:lists"]) + {:ok, list} = Pleroma.List.create("name", user) + + conn = + conn + |> assign(:user, user) + |> get("/api/v1/lists/#{list.id}") + + assert %{"id" => id} = json_response(conn, 200) + assert id == to_string(list.id) + end + + test "renders 404 if list is not found" do + %{conn: conn} = oauth_access(["read:lists"]) + + conn = get(conn, "/api/v1/lists/666") + + assert %{"error" => "List not found"} = json_response(conn, :not_found) + end + + test "renaming a list" do + %{user: user, conn: conn} = oauth_access(["write:lists"]) + {:ok, list} = Pleroma.List.create("name", user) + + 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" 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" => " "}) + + assert %{"error" => "can't be blank"} == json_response(conn, :unprocessable_entity) + end + + test "deleting a list" do + %{user: user, conn: conn} = oauth_access(["write:lists"]) + {:ok, list} = Pleroma.List.create("name", user) + + conn = delete(conn, "/api/v1/lists/#{list.id}") + + assert %{} = json_response(conn, 200) + assert is_nil(Repo.get(Pleroma.List, list.id)) + end +end 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..1fcad873d --- /dev/null +++ b/test/web/mastodon_api/controllers/marker_controller_test.exs @@ -0,0 +1,124 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 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..042511ca4 --- /dev/null +++ b/test/web/mastodon_api/controllers/media_controller_test.exs @@ -0,0 +1,82 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 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..6f0606250 --- /dev/null +++ b/test/web/mastodon_api/controllers/notification_controller_test.exs @@ -0,0 +1,490 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 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..5a1cea11b --- /dev/null +++ b/test/web/mastodon_api/controllers/poll_controller_test.exs @@ -0,0 +1,157 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 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..53c132ff4 --- /dev/null +++ b/test/web/mastodon_api/controllers/report_controller_test.exs @@ -0,0 +1,78 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 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 +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..9666a7f2e --- /dev/null +++ b/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs @@ -0,0 +1,93 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 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 + + 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 + %{user: user, conn: conn} = oauth_access(["write:statuses"]) + scheduled_activity = insert(:scheduled_activity, user: user) + + new_scheduled_at = + NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) + + 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) + + 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 + %{user: user, conn: conn} = oauth_access(["write:statuses"]) + 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 diff --git a/test/web/mastodon_api/search_controller_test.exs b/test/web/mastodon_api/controllers/search_controller_test.exs index 043b96c14..effae130c 100644 --- a/test/web/mastodon_api/search_controller_test.exs +++ b/test/web/mastodon_api/controllers/search_controller_test.exs @@ -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) @@ -95,6 +124,17 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do assert user_three.nickname in result_ids end + + test "returns account if query contains a space", %{conn: conn} do + insert(:user, %{nickname: "shp@shitposter.club"}) + + results = + conn + |> get("/api/v1/accounts/search", %{"q" => "shp@shitposter.club xxx "}) + |> json_response(200) + + assert length(results) == 1 + end end describe ".search" do @@ -131,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) @@ -146,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" @@ -169,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) @@ -182,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..b03b4b344 --- /dev/null +++ b/test/web/mastodon_api/controllers/status_controller_test.exs @@ -0,0 +1,1226 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 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]) + + 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 "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"] + 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 "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 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 "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 + + test "returns 400 error when activity is not exist", %{conn: conn} do + conn = post(conn, "/api/v1/statuses/foo/reblog") + + assert json_response(conn, 400) == %{"error" => "Could not repeat"} + 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 400 error when activity is not exist", %{conn: conn} do + conn = post(conn, "/api/v1/statuses/foo/unreblog") + + assert json_response(conn, 400) == %{"error" => "Could not unrepeat"} + 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 400 error for a wrong id", %{conn: conn} do + conn = post(conn, "/api/v1/statuses/1/favourite") + + assert json_response(conn, 400) == %{"error" => "Could not favorite"} + 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 400 error for a wrong id", %{conn: conn} do + conn = post(conn, "/api/v1/statuses/1/unfavourite") + + assert json_response(conn, 400) == %{"error" => "Could not unfavorite"} + 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 +end diff --git a/test/web/mastodon_api/subscription_controller_test.exs b/test/web/mastodon_api/controllers/subscription_controller_test.exs index 7dfb02f63..7dfb02f63 100644 --- a/test/web/mastodon_api/subscription_controller_test.exs +++ b/test/web/mastodon_api/controllers/subscription_controller_test.exs 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..0319d3475 --- /dev/null +++ b/test/web/mastodon_api/controllers/suggestion_controller_test.exs @@ -0,0 +1,46 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 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..bb94d8e5a --- /dev/null +++ b/test/web/mastodon_api/controllers/timeline_controller_test.exs @@ -0,0 +1,289 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 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/list_view_test.exs b/test/web/mastodon_api/list_view_test.exs deleted file mode 100644 index 73143467f..000000000 --- a/test/web/mastodon_api/list_view_test.exs +++ /dev/null @@ -1,22 +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.MastodonAPI.ListViewTest do - use Pleroma.DataCase - import Pleroma.Factory - alias Pleroma.Web.MastodonAPI.ListView - - test "Represent a list" do - user = insert(:user) - title = "mortal enemies" - {:ok, list} = Pleroma.List.create(title, user) - - expected = %{ - id: to_string(list.id), - title: title - } - - assert expected == ListView.render("list.json", %{list: list}) - end -end diff --git a/test/web/mastodon_api/mastodon_api_controller/update_credentials_test.exs b/test/web/mastodon_api/mastodon_api_controller/update_credentials_test.exs deleted file mode 100644 index 71d0c8af8..000000000 --- a/test/web/mastodon_api/mastodon_api_controller/update_credentials_test.exs +++ /dev/null @@ -1,304 +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.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do - alias Pleroma.Repo - alias Pleroma.User - - use Pleroma.Web.ConnCase - - import Pleroma.Factory - - describe "updating credentials" do - test "sets user settings in a generic way", %{conn: conn} do - user = insert(:user) - - res_conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{ - "pleroma_settings_store" => %{ - pleroma_fe: %{ - theme: "bla" - } - } - }) - - assert user = json_response(res_conn, 200) - assert user["pleroma"]["settings_store"] == %{"pleroma_fe" => %{"theme" => "bla"}} - - user = Repo.get(User, user["id"]) - - res_conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{ - "pleroma_settings_store" => %{ - masto_fe: %{ - theme: "bla" - } - } - }) - - assert user = json_response(res_conn, 200) - - assert user["pleroma"]["settings_store"] == - %{ - "pleroma_fe" => %{"theme" => "bla"}, - "masto_fe" => %{"theme" => "bla"} - } - - user = Repo.get(User, user["id"]) - - res_conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{ - "pleroma_settings_store" => %{ - masto_fe: %{ - theme: "blub" - } - } - }) - - assert user = json_response(res_conn, 200) - - assert user["pleroma"]["settings_store"] == - %{ - "pleroma_fe" => %{"theme" => "bla"}, - "masto_fe" => %{"theme" => "blub"} - } - 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", %{ - "note" => "I drink #cofe with @#{user2.nickname}" - }) - - assert user = 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>) - end - - test "updates the user's locking status", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{locked: "true"}) - - assert user = json_response(conn, 200) - assert user["locked"] == true - 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"}) - - assert user = json_response(conn, 200) - assert user["source"]["privacy"] == "cofe" - end - - test "updates the user's hide_followers status", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{hide_followers: "true"}) - - assert user = json_response(conn, 200) - assert user["pleroma"]["hide_followers"] == true - end - - test "updates the user's skip_thread_containment option", %{conn: conn} do - user = insert(:user) - - 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 - end - - test "updates the user's hide_follows status", %{conn: conn} do - user = insert(:user) - - 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 - 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"}) - - assert user = json_response(conn, 200) - assert user["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"}) - - assert user = json_response(conn, 200) - assert user["source"]["pleroma"]["show_role"] == false - end - - test "updates the user's no_rich_text status", %{conn: conn} do - user = insert(:user) - - 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 - 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"}) - - assert user = json_response(conn, 200) - assert user["display_name"] == "markorepairs" - end - - test "updates the user's avatar", %{conn: conn} do - user = insert(:user) - - 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}) - - 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) - - 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}) - - 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"), - filename: "an_image.jpg" - } - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{ - "pleroma_background_image" => new_header - }) - - assert user_response = json_response(conn, 200) - assert user_response["pleroma"]["background_image"] - end - - test "requires 'write' permission", %{conn: conn} do - token1 = insert(:oauth_token, scopes: ["read"]) - token2 = insert(:oauth_token, scopes: ["write", "follow"]) - - for token <- [token1, token2] do - conn = - 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) - else - assert json_response(conn, 200) - end - end - end - - test "updates profile emojos", %{conn: conn} do - user = insert(:user) - - note = "*sips :blank:*" - name = "I am :firefox:" - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{ - "note" => note, - "display_name" => name - }) - - assert json_response(conn, 200) - - conn = - conn - |> get("/api/v1/accounts/#{user.id}") - - assert user = json_response(conn, 200) - - assert user["note"] == note - assert user["display_name"] == name - assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = user["emojis"] - 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 b4b1dd785..c1f70f9fe 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -5,3858 +5,37 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do use Pleroma.Web.ConnCase - alias Ecto.Changeset - alias Pleroma.Activity - 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 - alias Pleroma.Web.TwitterAPI.TwitterAPI - import Pleroma.Factory - import ExUnit.CaptureLog - import Tesla.Mock - import Swoosh.TestAssertions + describe "empty_array/2 (stubs)" do + test "GET /api/v1/accounts/:id/identity_proofs" do + %{user: user, conn: conn} = oauth_access(["n/a"]) - @image "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7" - - setup do - mock(fn env -> apply(HttpRequestMock, :request, [env]) end) - :ok - end - - 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 - public = Pleroma.Config.get([:instance, :public]) - Pleroma.Config.put([:instance, :public], false) - - on_exit(fn -> - Pleroma.Config.put([:instance, :public], public) - end) - - assert conn - |> get("/api/v1/timelines/public", %{"local" => "False"}) - |> json_response(403) == %{"error" => "This resource requires authentication."} - 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 - 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 - Pleroma.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) - Pleroma.Config.put([:rich_media, :enabled], false) - 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 = + res = 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 = Pleroma.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 = Pleroma.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 = Pleroma.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 = Pleroma.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 his 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 - - 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") + |> get("/api/v1/accounts/#{user.id}/identity_proofs") |> 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 "lists" do - test "creating a list", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/lists", %{"title" => "cuties"}) - - assert %{"title" => title} = json_response(conn, 200) - assert title == "cuties" - end - - test "adding users to a list", %{conn: conn} do - user = insert(:user) - 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]}) - - 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) - 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]}) - - 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) - other_user = insert(:user) - {:ok, list} = Pleroma.List.create("name", user) - {:ok, list} = Pleroma.List.follow(list, other_user) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) - - assert [%{"id" => id}] = json_response(conn, 200) - assert id == to_string(other_user.id) - end - - test "retrieving a list", %{conn: conn} do - user = insert(:user) - {:ok, list} = Pleroma.List.create("name", user) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/lists/#{list.id}") - - assert %{"id" => id} = json_response(conn, 200) - assert id == to_string(list.id) - end - - test "renaming a list", %{conn: conn} do - user = insert(:user) - {:ok, list} = Pleroma.List.create("name", user) - - conn = - conn - |> assign(:user, user) - |> put("/api/v1/lists/#{list.id}", %{"title" => "newname"}) - - assert %{"title" => name} = json_response(conn, 200) - assert name == "newname" - end - - test "deleting a list", %{conn: conn} do - user = insert(:user) - {:ok, list} = Pleroma.List.create("name", user) - - conn = - conn - |> assign(:user, user) - |> delete("/api/v1/lists/#{list.id}") - - assert %{} = json_response(conn, 200) - assert is_nil(Repo.get(Pleroma.List, list.id)) - end - - 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 + assert res == [] 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"}) + test "GET /api/v1/endorsements" do + %{conn: conn} = oauth_access(["read:accounts"]) - 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 = + res = 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" - } - - media = - TwitterAPI.upload(file, user, "json") - |> Jason.decode!() - - {:ok, image_post} = - CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media["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 - upload_config = Pleroma.Config.get([Pleroma.Upload]) - proxy_config = Pleroma.Config.get([:media_proxy]) - - on_exit(fn -> - Pleroma.Config.put([Pleroma.Upload], upload_config) - Pleroma.Config.put([:media_proxy], proxy_config) - end) - - 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 - - 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 - - test "returns proxied url when media proxy is enabled", %{conn: conn, image: image} do - Pleroma.Config.put([Pleroma.Upload, :base_url], "https://media.pleroma.social") - - proxy_url = "https://cache.pleroma.social" - Pleroma.Config.put([:media_proxy, :enabled], true) - Pleroma.Config.put([:media_proxy, :base_url], proxy_url) - - media = - conn - |> post("/api/v1/media", %{"file" => image}) - |> json_response(:ok) - - assert String.starts_with?(media["url"], proxy_url) - end - - test "returns media url when proxy is enabled but media url is whitelisted", %{ - conn: conn, - image: image - } do - media_url = "https://media.pleroma.social" - Pleroma.Config.put([Pleroma.Upload, :base_url], media_url) - - Pleroma.Config.put([:media_proxy, :enabled], true) - Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social") - Pleroma.Config.put([:media_proxy, :whitelist], ["media.pleroma.social"]) - - media = - conn - |> post("/api/v1/media", %{"file" => image}) - |> json_response(:ok) - - assert String.starts_with?(media["url"], media_url) - 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 - - test "account fetching", %{conn: conn} do - user = insert(:user) - - conn = - 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 "account fetching also works nickname", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> get("/api/v1/accounts/#{user.nickname}") - - assert %{"id" => id} = json_response(conn, 200) - assert id == user.id - 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 = 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" => _ - } = 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.update_stats() - - 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.update_stats() - - 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 - Pleroma.Config.put([:instance, :max_pinned_statuses], 1) - - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"}) - - [user: user, activity: activity] - 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") + |> get("/api/v1/endorsements") |> 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"} + assert res == [] 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 - Pleroma.Config.put([:rich_media, :enabled], true) - - on_exit(fn -> - Pleroma.Config.put([:rich_media, :enabled], false) - end) - - 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" => "www.imdb.com", - "provider_url" => "http://www.imdb.com", - "title" => "The Rock", - "type" => "link", - "url" => "http://www.imdb.com/title/tt0117500/", - "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" => "http://www.imdb.com/title/tt0117500/", - "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 = + test "GET /api/v1/trends", %{conn: conn} do + res = conn - |> assign(:user, user) - |> get("/api/v1/statuses/#{activity.id}/card") + |> get("/api/v1/trends") |> 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" => "pleroma.social", - "provider_url" => "https://pleroma.social", - "url" => "https://pleroma.social/", - "pleroma" => %{ - "opengraph" => %{ - "title" => "Pleroma", - "type" => "website", - "url" => "https://pleroma.social/" - } - } - } - 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 - user = insert(:user) - {:ok, activity} = CommonAPI.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 = 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 - |> 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 "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] - 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 - 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] - 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 - 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 = Pleroma.Config.get([:instance, :notify_email]) - instance_name = Pleroma.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 == "" + 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 new file mode 100644 index 000000000..561ef05aa --- /dev/null +++ b/test/web/mastodon_api/mastodon_api_test.exs @@ -0,0 +1,104 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.MastodonAPITest do + use Pleroma.Web.ConnCase + + alias Pleroma.Notification + alias Pleroma.ScheduledActivity + alias Pleroma.User + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MastodonAPI.MastodonAPI + + import Pleroma.Factory + + describe "follow/3" do + test "returns error when followed user is deactivated" do + follower = insert(:user) + user = insert(:user, local: true, deactivated: true) + {:error, error} = MastodonAPI.follow(follower, user) + assert error == "Could not follow user: #{user.nickname} is deactivated." + end + + test "following for user" do + follower = insert(:user) + user = insert(:user) + {:ok, follower} = MastodonAPI.follow(follower, user) + assert User.following?(follower, user) + end + + test "returns ok if user already followed" do + follower = insert(:user) + user = insert(:user) + {:ok, follower} = User.follow(follower, user) + {:ok, follower} = MastodonAPI.follow(follower, refresh_record(user)) + assert User.following?(follower, user) + end + end + + describe "get_followers/2" do + test "returns user followers" do + follower1_user = insert(:user) + follower2_user = insert(:user) + user = insert(:user) + {:ok, _follower1_user} = User.follow(follower1_user, user) + {:ok, follower2_user} = User.follow(follower2_user, user) + + assert MastodonAPI.get_followers(user, %{"limit" => 1}) == [follower2_user] + end + end + + describe "get_friends/2" do + test "returns user friends" do + user = insert(:user) + followed_one = insert(:user) + followed_two = insert(:user) + followed_three = insert(:user) + + {:ok, user} = User.follow(user, followed_one) + {:ok, user} = User.follow(user, followed_two) + {:ok, user} = User.follow(user, followed_three) + res = MastodonAPI.get_friends(user) + + assert length(res) == 3 + assert Enum.member?(res, refresh_record(followed_three)) + assert Enum.member?(res, refresh_record(followed_two)) + assert Enum.member?(res, refresh_record(followed_one)) + end + end + + describe "get_notifications/2" do + test "returns notifications for user" do + user = insert(:user) + subscriber = insert(:user) + + User.subscribe(subscriber, user) + + {:ok, status} = CommonAPI.post(user, %{"status" => "Akariiiin"}) + + {:ok, status1} = CommonAPI.post(user, %{"status" => "Magi"}) + {:ok, [notification]} = Notification.create_notifications(status) + {:ok, [notification1]} = Notification.create_notifications(status1) + res = MastodonAPI.get_notifications(subscriber) + + assert Enum.member?(Enum.map(res, & &1.id), notification.id) + assert Enum.member?(Enum.map(res, & &1.id), notification1.id) + end + end + + describe "get_scheduled_activities/2" do + test "returns user scheduled activities" do + user = insert(:user) + + today = + NaiveDateTime.utc_now() + |> NaiveDateTime.add(:timer.minutes(6), :millisecond) + |> NaiveDateTime.to_iso8601() + + attrs = %{params: %{}, scheduled_at: today} + {:ok, schedule} = ScheduledActivity.create(user, attrs) + assert MastodonAPI.get_scheduled_activities(user) == [schedule] + end + end +end diff --git a/test/web/mastodon_api/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs index fa44d35cc..2107bb85c 100644 --- a/test/web/mastodon_api/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-2019 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,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do source: %{ note: "valid html", sensitive: false, - pleroma: %{} + pleroma: %{ + actor_type: "Person", + discoverable: false + }, + fields: [] }, pleroma: %{ background_image: "https://example.com/images/asuka_hospital.png", @@ -78,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] }) @@ -134,7 +135,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do source: %{ note: user.bio, sensitive: false, - pleroma: %{} + pleroma: %{ + actor_type: "Service", + discoverable: false + }, + fields: [] }, pleroma: %{ background_image: nil, @@ -145,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 @@ -180,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), @@ -208,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), @@ -231,9 +238,19 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do AccountView.render("relationship.json", %{user: user, target: other_user}) end + test "represent a relationship for the user blocking a domain" do + user = insert(:user) + other_user = insert(:user, ap_id: "https://bad.site/users/other_user") + + {:ok, user} = User.block_domain(user, "bad.site") + + assert %{domain_blocking: true, blocking: false} = + AccountView.render("relationship.json", %{user: user, target: other_user}) + end + 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) @@ -262,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 = %{ @@ -294,7 +314,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do source: %{ note: user.bio, sensitive: false, - pleroma: %{} + pleroma: %{ + actor_type: "Service", + discoverable: false + }, + fields: [] }, pleroma: %{ background_image: nil, @@ -305,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, @@ -323,27 +349,179 @@ 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 user = insert(:user, name: "<marquee> username </marquee>") - result = AccountView.render("account.json", %{user: user}) + result = AccountView.render("show.json", %{user: user}) refute 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/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) + + 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("show.json", %{user: user}) + end + + test "shows actual follower/following count to the account owner" 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 + } = 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 new file mode 100644 index 000000000..6ed22597d --- /dev/null +++ b/test/web/mastodon_api/views/conversation_view_test.exs @@ -0,0 +1,35 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.ConversationViewTest do + use Pleroma.DataCase + + alias Pleroma.Conversation.Participation + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MastodonAPI.ConversationView + + import Pleroma.Factory + + test "represents a Mastodon Conversation entity" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{"status" => "hey @#{other_user.nickname}", "visibility" => "direct"}) + + [participation] = Participation.for_user_with_last_activity_id(user) + + assert participation + + conversation = + ConversationView.render("participation.json", %{participation: participation, for: user}) + + assert conversation.id == participation.id |> to_string() + assert conversation.last_status.id == activity.id + + 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 new file mode 100644 index 000000000..59e896a7c --- /dev/null +++ b/test/web/mastodon_api/views/list_view_test.exs @@ -0,0 +1,32 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.ListViewTest do + use Pleroma.DataCase + import Pleroma.Factory + alias Pleroma.Web.MastodonAPI.ListView + + test "show" do + user = insert(:user) + title = "mortal enemies" + {:ok, list} = Pleroma.List.create(title, user) + + expected = %{ + id: to_string(list.id), + title: title + } + + assert expected == ListView.render("show.json", %{list: list}) + end + + test "index" do + user = insert(:user) + + {:ok, list} = Pleroma.List.create("my list", user) + {:ok, list2} = Pleroma.List.create("cofe", user) + + assert [%{id: _, title: "my list"}, %{id: _, title: "cofe"}] = + ListView.render("index.json", lists: [list, list2]) + end +end 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..8a5c89d56 --- /dev/null +++ b/test/web/mastodon_api/views/marker_view_test.exs @@ -0,0 +1,27 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 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/notification_view_test.exs b/test/web/mastodon_api/views/notification_view_test.exs index 977ea1e87..1fe83cb2c 100644 --- a/test/web/mastodon_api/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-2019 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) } @@ -100,5 +100,65 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do NotificationView.render("index.json", %{notifications: [notification], for: followed}) assert [expected] == result + + User.perform(:delete, follower) + notification = Notification |> Repo.one() |> Repo.preload(:activity) + + 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 "EmojiReaction 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..8cd7636a5 --- /dev/null +++ b/test/web/mastodon_api/views/poll_view_test.exs @@ -0,0 +1,126 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 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/push_subscription_view_test.exs b/test/web/mastodon_api/views/push_subscription_view_test.exs index dc935fc82..4e4f5b7e6 100644 --- a/test/web/mastodon_api/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-2019 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/scheduled_activity_view_test.exs b/test/web/mastodon_api/views/scheduled_activity_view_test.exs index ecbb855d4..6387e4555 100644 --- a/test/web/mastodon_api/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-2019 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/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs index 3447c5b1f..25777b011 100644 --- a/test/web/mastodon_api/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-2019 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,6 +24,59 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do :ok end + 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] == [ + %{emoji: "☕", count: 2}, + %{emoji: "🍵", count: 1} + ] + 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("show.json", + activity: activity, + with_direct_conversation_id: true, + for: user + ) + + 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 user = insert(:user) @@ -31,7 +85,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 @@ -48,7 +102,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 @@ -66,7 +120,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 @@ -78,7 +132,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"] || "") @@ -88,12 +142,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, @@ -105,7 +159,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: [], @@ -132,8 +186,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, + emoji_reactions: [] } } @@ -144,27 +202,45 @@ 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 + test "tells if the message is thread muted" do + user = insert(:user) + other_user = insert(:user) + + {:ok, _user_relationships} = User.mute(user, other_user) + + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"}) + 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("show.json", %{activity: activity, for: user}) + + assert status.pleroma.thread_muted == true + end + test "tells if the status is bookmarked" 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 @@ -172,7 +248,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 @@ -184,7 +260,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) @@ -194,17 +270,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 @@ -227,7 +301,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 @@ -264,7 +338,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,13 +374,23 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do assert %{id: "2"} = StatusView.render("attachment.json", %{attachment: object}) end + test "put the url advertised in the Activity in to the url attribute" do + id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810" + [activity] = Activity.search(nil, id) + + status = StatusView.render("show.json", %{activity: activity}) + + assert status.uri == id + assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/" + end + test "a reblog" do user = insert(:user) activity = insert(:note_activity) {: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) @@ -323,12 +407,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 = [ @@ -405,108 +504,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do 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 - end - test "embeds a relationship in the account" do user = insert(:user) other_user = insert(:user) @@ -516,7 +513,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}) @@ -533,7 +530,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}) @@ -550,8 +547,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..fdfdb5ec6 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-2019 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do diff --git a/test/web/media_proxy/media_proxy_test.exs b/test/web/media_proxy/media_proxy_test.exs index edbbf9b66..96bdde219 100644 --- a/test/web/media_proxy/media_proxy_test.exs +++ b/test/web/media_proxy/media_proxy_test.exs @@ -1,17 +1,14 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MediaProxyTest do use ExUnit.Case + use Pleroma.Tests.Helpers import Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy.MediaProxyController - setup do - enabled = Pleroma.Config.get([:media_proxy, :enabled]) - on_exit(fn -> Pleroma.Config.put([:media_proxy, :enabled], enabled) end) - :ok - end + clear_config([:media_proxy, :enabled]) describe "when enabled" do setup do @@ -171,21 +168,6 @@ defmodule Pleroma.Web.MediaProxyTest do encoded = url(url) assert decode_result(encoded) == url end - - test "does not change whitelisted urls" do - upload_config = Pleroma.Config.get([Pleroma.Upload]) - media_url = "https://media.pleroma.social" - Pleroma.Config.put([Pleroma.Upload, :base_url], media_url) - Pleroma.Config.put([:media_proxy, :whitelist], ["media.pleroma.social"]) - Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social") - - url = "#{media_url}/static/logo.png" - encoded = url(url) - - assert String.starts_with?(encoded, media_url) - - Pleroma.Config.put([Pleroma.Upload], upload_config) - end end describe "when disabled" do @@ -215,12 +197,43 @@ defmodule Pleroma.Web.MediaProxyTest do decoded end - test "mediaproxy whitelist" do - Pleroma.Config.put([:media_proxy, :enabled], true) - Pleroma.Config.put([:media_proxy, :whitelist], ["google.com", "feld.me"]) - url = "https://feld.me/foo.png" + describe "whitelist" do + setup do + Pleroma.Config.put([:media_proxy, :enabled], true) + :ok + end + + test "mediaproxy whitelist" do + Pleroma.Config.put([:media_proxy, :whitelist], ["google.com", "feld.me"]) + url = "https://feld.me/foo.png" + + unencoded = url(url) + assert unencoded == url + end - unencoded = url(url) - assert unencoded == url + test "does not change whitelisted urls" do + Pleroma.Config.put([:media_proxy, :whitelist], ["mycdn.akamai.com"]) + Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social") + + media_url = "https://mycdn.akamai.com" + + url = "#{media_url}/static/logo.png" + encoded = url(url) + + assert String.starts_with?(encoded, media_url) + 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) + + url = "#{media_url}/static/logo.png" + 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..50e9ce52e --- /dev/null +++ b/test/web/metadata/feed_test.exs @@ -0,0 +1,18 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 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/twitter_card_test.exs b/test/web/metadata/twitter_card_test.exs index 0814006d2..85a654f52 100644 --- a/test/web/metadata/twitter_card_test.exs +++ b/test/web/metadata/twitter_card_test.exs @@ -26,7 +26,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 +92,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 new file mode 100644 index 000000000..7547f2932 --- /dev/null +++ b/test/web/metadata/utils_test.exs @@ -0,0 +1,32 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Metadata.UtilsTest do + use Pleroma.DataCase + import Pleroma.Factory + alias Pleroma.Web.Metadata.Utils + + describe "scrub_html_and_truncate/1" do + test "it returns text without encode HTML" do + user = insert(:user) + + note = + insert(:note, %{ + data: %{ + "actor" => user.ap_id, + "id" => "https://pleroma.gov/objects/whatever", + "content" => "Pleroma's really cool!" + } + }) + + assert Utils.scrub_html_and_truncate(note) == "Pleroma's really cool!" + end + end + + describe "scrub_html_and_truncate/2" do + test "it returns text without encode HTML" do + assert Utils.scrub_html_and_truncate("Pleroma's really cool!") == "Pleroma's really cool!" + end + end +end diff --git a/test/web/node_info_test.exs b/test/web/node_info_test.exs index d7f848bfa..9a574a38d 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-2019 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.NodeInfoTest do @@ -24,8 +24,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 +61,33 @@ defmodule Pleroma.Web.NodeInfoTest do assert Pleroma.Application.repository() == result["software"]["repository"] end + test "returns fieldsLimits field", %{conn: conn} do + max_account_fields = Pleroma.Config.get([:instance, :max_account_fields]) + max_remote_account_fields = Pleroma.Config.get([:instance, :max_remote_account_fields]) + account_field_name_length = Pleroma.Config.get([:instance, :account_field_name_length]) + account_field_value_length = Pleroma.Config.get([:instance, :account_field_value_length]) + + 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 + + Pleroma.Config.put([:instance, :max_account_fields], max_account_fields) + Pleroma.Config.put([:instance, :max_remote_account_fields], max_remote_account_fields) + Pleroma.Config.put([:instance, :account_field_name_length], account_field_name_length) + Pleroma.Config.put([:instance, :account_field_value_length], account_field_value_length) + 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,7 +111,34 @@ 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]) + + Pleroma.Config.put([:instance, :federating], true) + + response = + conn + |> get("/nodeinfo/2.1.json") + |> json_response(:ok) + + assert response["metadata"]["federation"]["enabled"] == true + + Pleroma.Config.put([:instance, :federating], false) + + response = + conn + |> get("/nodeinfo/2.1.json") + |> json_response(:ok) + + assert response["metadata"]["federation"]["enabled"] == false + + Pleroma.Config.put([:instance, :federating], original) + end + test "it shows MRF transparency data if enabled", %{conn: conn} do + config = Pleroma.Config.get([:instance, :rewrite_policy]) + Pleroma.Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy]) + option = Pleroma.Config.get([:instance, :mrf_transparency]) Pleroma.Config.put([:instance, :mrf_transparency], true) @@ -98,11 +152,15 @@ defmodule Pleroma.Web.NodeInfoTest do assert response["metadata"]["federation"]["mrf_simple"] == simple_config + Pleroma.Config.put([:instance, :rewrite_policy], config) Pleroma.Config.put([:instance, :mrf_transparency], option) Pleroma.Config.put(:mrf_simple, %{}) end test "it performs exclusions from MRF transparency data if configured", %{conn: conn} do + config = Pleroma.Config.get([:instance, :rewrite_policy]) + Pleroma.Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy]) + option = Pleroma.Config.get([:instance, :mrf_transparency]) Pleroma.Config.put([:instance, :mrf_transparency], true) @@ -122,6 +180,7 @@ defmodule Pleroma.Web.NodeInfoTest do assert response["metadata"]["federation"]["mrf_simple"] == expected_config assert response["metadata"]["federation"]["exclusions"] == true + Pleroma.Config.put([:instance, :rewrite_policy], config) Pleroma.Config.put([:instance, :mrf_transparency], option) Pleroma.Config.put([:instance, :mrf_transparency_exclusions], exclusions) Pleroma.Config.put(:mrf_simple, %{}) diff --git a/test/web/oauth/app_test.exs b/test/web/oauth/app_test.exs new file mode 100644 index 000000000..195b8c17f --- /dev/null +++ b/test/web/oauth/app_test.exs @@ -0,0 +1,33 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 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..2e82a7b79 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-2019 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 0eb191c76..1cbe133b7 100644 --- a/test/web/oauth/ldap_authorization_test.exs +++ b/test/web/oauth/ldap_authorization_test.exs @@ -12,21 +12,12 @@ defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do @skip if !Code.ensure_loaded?(:eldap), do: :skip - setup_all do - ldap_authenticator = - Pleroma.Config.get(Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.PleromaAuthenticator) - - ldap_enabled = Pleroma.Config.get([:ldap, :enabled]) - - on_exit(fn -> - Pleroma.Config.put(Pleroma.Web.Auth.Authenticator, ldap_authenticator) - Pleroma.Config.put([:ldap, :enabled], ldap_enabled) - end) - - Pleroma.Config.put(Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.LDAPAuthenticator) + clear_config_all([:ldap, :enabled]) do Pleroma.Config.put([:ldap, :enabled], true) + end - :ok + clear_config_all(Pleroma.Web.Auth.Authenticator) do + Pleroma.Config.put(Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.LDAPAuthenticator) end @tag @skip diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs index aae34804d..adeff8e25 100644 --- a/test/web/oauth/oauth_controller_test.exs +++ b/test/web/oauth/oauth_controller_test.exs @@ -1,35 +1,26 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OAuth.OAuthControllerTest do use Pleroma.Web.ConnCase import Pleroma.Factory - import Mock - alias Pleroma.Registration alias Pleroma.Repo + alias Pleroma.User alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.OAuthController alias Pleroma.Web.OAuth.Token - @oauth_config_path [:oauth2, :issue_new_refresh_token] @session_opts [ store: :cookie, key: "_test", signing_salt: "cooldude" ] + clear_config_all([:instance, :account_activation_required]) describe "in OAuth consumer mode, " do setup do - oauth_consumer_strategies_path = [:auth, :oauth_consumer_strategies] - oauth_consumer_strategies = Pleroma.Config.get(oauth_consumer_strategies_path) - Pleroma.Config.put(oauth_consumer_strategies_path, ~w(twitter facebook)) - - on_exit(fn -> - Pleroma.Config.put(oauth_consumer_strategies_path, oauth_consumer_strategies) - end) - [ app: insert(:oauth_app), conn: @@ -39,6 +30,13 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do ] end + clear_config([:auth, :oauth_consumer_strategies]) do + Pleroma.Config.put( + [:auth, :oauth_consumer_strategies], + ~w(twitter facebook) + ) + end + test "GET /oauth/authorize renders auth forms, including OAuth consumer form", %{ app: app, conn: conn @@ -108,28 +106,26 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do "state" => "" } - with_mock Pleroma.Web.Auth.Authenticator, - get_registration: fn _ -> {:ok, registration} end do - conn = - get( - conn, - "/oauth/twitter/callback", - %{ - "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", - "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", - "provider" => "twitter", - "state" => Poison.encode!(state_params) - } - ) + conn = + conn + |> assign(:ueberauth_auth, %{provider: registration.provider, uid: registration.uid}) + |> get( + "/oauth/twitter/callback", + %{ + "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", + "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", + "provider" => "twitter", + "state" => Poison.encode!(state_params) + } + ) - assert response = html_response(conn, 302) - assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/ - end + assert response = html_response(conn, 302) + assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/ end test "with user-unbound registration, GET /oauth/<provider>/callback renders registration_details page", %{app: app, conn: conn} do - registration = insert(:registration, user: nil) + user = insert(:user) state_params = %{ "scope" => "read write", @@ -138,26 +134,28 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do "state" => "a_state" } - with_mock Pleroma.Web.Auth.Authenticator, - get_registration: fn _ -> {:ok, registration} end do - conn = - get( - conn, - "/oauth/twitter/callback", - %{ - "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", - "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", - "provider" => "twitter", - "state" => Poison.encode!(state_params) - } - ) + conn = + conn + |> assign(:ueberauth_auth, %{ + provider: "twitter", + uid: "171799000", + info: %{nickname: user.nickname, email: user.email, name: user.name, description: nil} + }) + |> get( + "/oauth/twitter/callback", + %{ + "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", + "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", + "provider" => "twitter", + "state" => Poison.encode!(state_params) + } + ) - assert response = html_response(conn, 200) - assert response =~ ~r/name="op" type="submit" value="register"/ - assert response =~ ~r/name="op" type="submit" value="connect"/ - assert response =~ Registration.email(registration) - assert response =~ Registration.nickname(registration) - end + assert response = html_response(conn, 200) + assert response =~ ~r/name="op" type="submit" value="register"/ + assert response =~ ~r/name="op" type="submit" value="connect"/ + assert response =~ user.email + assert response =~ user.nickname end test "on authentication error, GET /oauth/<provider>/callback redirects to `redirect_uri`", %{ @@ -452,7 +450,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 @@ -471,12 +469,35 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do assert html_response(conn, 200) =~ ~s(type="submit") end + test "renders authentication page if user is already authenticated but user request with another client", + %{ + app: app, + conn: conn + } do + token = insert(:oauth_token, app: app) + + conn = + conn + |> put_session(:oauth_token, token.token) + |> get( + "/oauth/authorize", + %{ + "response_type" => "code", + "client_id" => "another_client_id", + "redirect_uri" => OAuthController.default_redirect_uri(app), + "scope" => "read" + } + ) + + assert html_response(conn, 200) =~ ~s(type="submit") + end + test "with existing authentication and non-OOB `redirect_uri`, redirects to app with `token` and `state` params", %{ app: app, conn: conn } do - token = insert(:oauth_token, app_id: app.id) + token = insert(:oauth_token, app: app) conn = conn @@ -502,7 +523,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 @@ -526,7 +547,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 @@ -546,33 +567,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"] - target = redirected_to(conn) - assert target =~ redirect_uri + # 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" + } + } + ) - query = URI.parse(target).query |> URI.query_decoder() |> Map.new() + target = redirected_to(conn) + assert target =~ redirect_uri - assert %{"state" => "statepassed", "code" => code} = query - auth = Repo.get_by(Authorization, token: code) - assert auth - assert auth.scopes == ["read", "write"] + 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 == expected_scopes + end end test "returns 401 for wrong credentials", %{conn: conn} do @@ -602,13 +636,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, @@ -629,7 +663,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) @@ -777,24 +811,15 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do end test "rejects token exchange for valid credentials belonging to unconfirmed user and confirmation is required" do - setting = Pleroma.Config.get([:instance, :account_activation_required]) - - unless setting do - Pleroma.Config.put([:instance, :account_activation_required], true) - on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end) - end - + 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) @@ -819,12 +844,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", @@ -833,10 +858,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 @@ -859,16 +943,10 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do end describe "POST /oauth/token - refresh token" do - setup do - oauth_token_config = Pleroma.Config.get(@oauth_config_path) - - on_exit(fn -> - Pleroma.Config.get(@oauth_config_path, oauth_token_config) - end) - end + clear_config([:oauth2, :issue_new_refresh_token]) test "issues a new access token with keep fresh token" do - Pleroma.Config.put(@oauth_config_path, true) + Pleroma.Config.put([:oauth2, :issue_new_refresh_token], true) user = insert(:user) app = insert(:oauth_app, scopes: ["read", "write"]) @@ -908,7 +986,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do end test "issues a new access token with new fresh token" do - Pleroma.Config.put(@oauth_config_path, false) + Pleroma.Config.put([:oauth2, :issue_new_refresh_token], false) user = insert(:user) app = insert(:oauth_app, scopes: ["read", "write"]) diff --git a/test/web/oauth/token/utils_test.exs b/test/web/oauth/token/utils_test.exs index 20e338cab..dc1f9a986 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-2019 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..5359940f8 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-2019 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 bb7648bdd..50235dfef 100644 --- a/test/web/ostatus/ostatus_controller_test.exs +++ b/test/web/ostatus/ostatus_controller_test.exs @@ -1,260 +1,280 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 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) - - config_path = [:instance, :federating] - initial_setting = Pleroma.Config.get(config_path) - - Pleroma.Config.put(config_path, true) - on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end) - :ok end - describe "salmon_incoming" do - test "decodes a salmon", %{conn: conn} do - user = insert(:user) - salmon = File.read!("test/fixtures/salmon.xml") + clear_config_all([:instance, :federating]) do + Pleroma.Config.put([:instance, :federating], true) + end - assert capture_log(fn -> - conn = - conn - |> put_req_header("content-type", "application/atom+xml") - |> post("/users/#{user.nickname}/salmon", salmon) + describe "GET object/2" do + test "redirects to /notice/id for html format", %{conn: conn} do + note_activity = insert(:note_activity) + object = Object.normalize(note_activity) + [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) + url = "/objects/#{uuid}" - assert response(conn, 200) - end) =~ "[error]" - end + conn = + conn + |> put_req_header("accept", "text/html") + |> get(url) - 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]" + assert redirected_to(conn) == "/notice/#{note_activity.id}" 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"]) + test "404s on private objects", %{conn: conn} do + note_activity = insert(:direct_note_activity) + object = Object.normalize(note_activity) + [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) - conn = conn - |> put_req_header("content-type", "application/atom+xml") - |> get("/users/#{user.nickname}/feed.atom") + |> get("/objects/#{uuid}") + |> response(404) + end - assert response(conn, 200) =~ object.data["content"] + test "404s on nonexisting objects", %{conn: conn} do + conn + |> get("/objects/123") + |> response(404) + end 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") + describe "GET activity/2" do + 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"])) - assert response(conn, 404) - end + conn = + conn + |> put_req_header("accept", "text/html") + |> get("/activities/#{uuid}") - 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}" + assert redirected_to(conn) == "/notice/#{note_activity.id}" + 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"])) - conn = conn - |> put_req_header("accept", "application/xml") - |> get(url) + |> get("/activities/#{uuid}") + |> response(404) + end - expected = - ActivityRepresenter.to_simple_form(note_activity, user, true) - |> ActivityRepresenter.wrap_with_entry() - |> :xmerl.export_simple(:xmerl_xml) - |> to_string + test "404s on nonexistent activities", %{conn: conn} do + conn + |> get("/activities/123") + |> response(404) + end - assert response(conn, 200) == expected - end + test "gets an activity in AS2 format", %{conn: conn} do + note_activity = insert(:note_activity) + [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) + url = "/activities/#{uuid}" - test "404s on private objects", %{conn: conn} do - note_activity = insert(:direct_note_activity) - object = Object.normalize(note_activity) - [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) + conn = + conn + |> put_req_header("accept", "application/activity+json") + |> get(url) - conn - |> get("/objects/#{uuid}") - |> response(404) + assert json_response(conn, 200) + end end - test "404s on nonexisting objects", %{conn: conn} do - conn - |> get("/objects/123") - |> response(404) - end + describe "GET notice/2" 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"] - 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"])) + redirect_url = + conn + |> put_req_header("accept", "application/activity+json") + |> get("/notice/#{note_activity.id}") + |> redirected_to() - conn - |> put_req_header("accept", "application/xml") - |> get("/activities/#{uuid}") - |> response(200) - end + assert redirect_url == expected_redirect_url + 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"])) + 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/xml") - |> get("/objects/#{uuid}") - |> response(200) + conn + |> put_req_header("accept", "application/activity+json") + |> get("/notice/#{note_activity.id}") + |> response(404) + end - Object.delete(object) + test "500s when actor not found", %{conn: conn} do + note_activity = insert(:note_activity) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) + User.invalidate_cache(user) + Pleroma.Repo.delete(user) - conn - |> put_req_header("accept", "application/xml") - |> get("/objects/#{uuid}") - |> response(404) - end + conn = + conn + |> get("/notice/#{note_activity.id}") - test "404s on private activities", %{conn: conn} do - note_activity = insert(:direct_note_activity) - [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) + assert response(conn, 500) == ~S({"error":"Something went wrong"}) + end - conn - |> get("/activities/#{uuid}") - |> response(404) - end + test "render html for redirect for html format", %{conn: conn} do + note_activity = insert(:note_activity) - test "404s on nonexistent activities", %{conn: conn} do - conn - |> get("/activities/123") - |> response(404) - end + resp = + conn + |> put_req_header("accept", "text/html") + |> get("/notice/#{note_activity.id}") + |> response(200) - test "gets a notice in xml format", %{conn: conn} do - note_activity = insert(:note_activity) + assert resp =~ + "<meta content=\"#{Pleroma.Web.base_url()}/notice/#{note_activity.id}\" property=\"og:url\">" - conn - |> get("/notice/#{note_activity.id}") - |> response(200) - end + user = insert(:user) - test "gets a notice in AS2 format", %{conn: conn} do - note_activity = insert(:note_activity) + {:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user) - conn - |> put_req_header("accept", "application/activity+json") - |> get("/notice/#{note_activity.id}") - |> json_response(200) - end + assert like_activity.data["type"] == "Like" - test "only gets a notice in AS2 format for Create messages", %{conn: conn} do - note_activity = insert(:note_activity) - url = "/notice/#{note_activity.id}" + resp = + conn + |> put_req_header("accept", "text/html") + |> get("/notice/#{like_activity.id}") + |> response(200) - conn = - conn - |> put_req_header("accept", "application/activity+json") - |> get(url) + assert resp =~ "<!--server-generated-meta-->" + end - assert json_response(conn, 200) + test "404s a private notice", %{conn: conn} do + note_activity = insert(:direct_note_activity) + url = "/notice/#{note_activity.id}" - user = insert(:user) + conn = + conn + |> get(url) - {:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user) - url = "/notice/#{like_activity.id}" + assert response(conn, 404) + end - assert like_activity.data["type"] == "Like" + test "404s a nonexisting notice", %{conn: conn} do + url = "/notice/123" - conn = - build_conn() - |> put_req_header("accept", "application/activity+json") - |> get(url) + conn = + conn + |> get(url) - assert response(conn, 404) + assert response(conn, 404) + end end - test "gets an activity in AS2 format", %{conn: conn} do - note_activity = insert(:note_activity) - [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) - url = "/activities/#{uuid}" + describe "GET /notice/:id/embed_player" do + test "render embed player", %{conn: conn} do + note_activity = insert(:note_activity) + object = Pleroma.Object.normalize(note_activity) + + 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() + + conn = + conn + |> get("/notice/#{note_activity.id}/embed_player") + + assert Plug.Conn.get_resp_header(conn, "x-frame-options") == ["ALLOW"] + + assert Plug.Conn.get_resp_header( + conn, + "content-security-policy" + ) == [ + "default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;" + ] + + assert response(conn, 200) =~ + "<video controls loop><source src=\"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4\" type=\"video/mp4\">Your browser does not support video/mp4 playback.</video>" + end - conn = - conn - |> put_req_header("accept", "application/activity+json") - |> get(url) + test "404s when activity isn't create", %{conn: conn} do + note_activity = insert(:note_activity, data_attrs: %{"type" => "Like"}) - assert json_response(conn, 200) - end + assert conn + |> get("/notice/#{note_activity.id}/embed_player") + |> response(404) + end - test "404s a private notice", %{conn: conn} do - note_activity = insert(:direct_note_activity) - url = "/notice/#{note_activity.id}" + test "404s when activity is direct message", %{conn: conn} do + note_activity = insert(:note_activity, data_attrs: %{"directMessage" => true}) - conn = - conn - |> get(url) + assert conn + |> get("/notice/#{note_activity.id}/embed_player") + |> response(404) + end - assert response(conn, 404) - end + test "404s when attachment is empty", %{conn: conn} do + note_activity = insert(:note_activity) + object = Pleroma.Object.normalize(note_activity) + object_data = Map.put(object.data, "attachment", []) - test "404s a nonexisting notice", %{conn: conn} do - url = "/notice/123" + object + |> Ecto.Changeset.change(data: object_data) + |> Pleroma.Repo.update() - conn = - conn - |> get(url) + assert conn + |> get("/notice/#{note_activity.id}/embed_player") + |> response(404) + end - assert response(conn, 404) + test "404s when attachment isn't audio or video", %{conn: conn} do + note_activity = insert(:note_activity) + object = Pleroma.Object.normalize(note_activity) + + object_data = + Map.put(object.data, "attachment", [ + %{ + "url" => [ + %{ + "href" => "https://peertube.moe/static/webseed/480.jpg", + "mediaType" => "image/jpg", + "type" => "Link" + } + ] + } + ]) + + object + |> Ecto.Changeset.change(data: object_data) + |> Pleroma.Repo.update() + + assert conn + |> get("/notice/#{note_activity.id}/embed_player") + |> response(404) + end end end diff --git a/test/web/ostatus/ostatus_test.exs b/test/web/ostatus/ostatus_test.exs deleted file mode 100644 index 4e8f3a0fc..000000000 --- a/test/web/ostatus/ostatus_test.exs +++ /dev/null @@ -1,581 +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 retweeted_activity.data["object"]["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 "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_user 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_user(author) - assert user.avatar["type"] == "Image" - assert user.name == old_name - assert user.bio == old_bio - - {:ok, user_again} = OStatus.find_make_or_update_user(author) - assert user_again == user - 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..d17026a6b --- /dev/null +++ b/test/web/pleroma_api/controllers/account_controller_test.exs @@ -0,0 +1,338 @@ +# 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.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..8e76f2f3d --- /dev/null +++ b/test/web/pleroma_api/controllers/emoji_api_controller_test.exs @@ -0,0 +1,467 @@ +# 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.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..40c33e609 --- /dev/null +++ b/test/web/pleroma_api/controllers/mascot_controller_test.exs @@ -0,0 +1,64 @@ +# 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.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..3978c2ec5 --- /dev/null +++ b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs @@ -0,0 +1,232 @@ +# 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.Object + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + test "POST /api/v1/pleroma/statuses/:id/react_with_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"])) + |> post("/api/v1/pleroma/statuses/#{activity.id}/react_with_emoji", %{"emoji" => "☕"}) + + assert %{"id" => id} = json_response(result, 200) + assert to_string(activity.id) == id + end + + test "POST /api/v1/pleroma/statuses/:id/unreact_with_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"])) + |> post("/api/v1/pleroma/statuses/#{activity.id}/unreact_with_emoji", %{"emoji" => "☕"}) + + 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/emoji_reactions_by", %{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}/emoji_reactions_by") + |> json_response(200) + + assert result == [] + + {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅") + + result = + conn + |> get("/api/v1/pleroma/statuses/#{activity.id}/emoji_reactions_by") + |> json_response(200) + + [%{"emoji" => "🎅", "count" => 1, "accounts" => [represented_user]}] = 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..2242610f1 --- /dev/null +++ b/test/web/pleroma_api/controllers/scrobble_controller_test.exs @@ -0,0 +1,58 @@ +# 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.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/plugs/federating_plug_test.exs b/test/web/plugs/federating_plug_test.exs index c01e01124..9dcab93da 100644 --- a/test/web/plugs/federating_plug_test.exs +++ b/test/web/plugs/federating_plug_test.exs @@ -1,18 +1,10 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.FederatingPlugTest do use Pleroma.Web.ConnCase - - setup_all do - config_path = [:instance, :federating] - initial_setting = Pleroma.Config.get(config_path) - - on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end) - - :ok - end + clear_config_all([: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 1e948086a..acae7a734 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-2019 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,7 @@ defmodule Pleroma.Web.Push.ImplTest do refute Pleroma.Repo.get(Subscription, subscription.id) end - test "renders body for create activity" do + test "renders title and body for create activity" do user = insert(:user, nickname: "Bob") {:ok, activity} = @@ -116,19 +117,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_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} = @@ -142,9 +148,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,7 +165,68 @@ defmodule Pleroma.Web.Push.ImplTest do {:ok, activity, _} = CommonAPI.favorite(activity.id, user) object = Object.normalize(activity) - assert Impl.format_body(%{activity: activity}, user, object) == - "@Bob has favorited your post" + 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 85515c432..77b5d5dc6 100644 --- a/test/web/rel_me_test.exs +++ b/test/web/rel_me_test.exs @@ -5,33 +5,8 @@ defmodule Pleroma.Web.RelMeTest do use ExUnit.Case, async: true - setup do - Tesla.Mock.mock(fn - %{ - method: :get, - url: "http://example.com/rel_me/anchor" - } -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_anchor.html")} - - %{ - method: :get, - url: "http://example.com/rel_me/anchor_nofollow" - } -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_anchor_nofollow.html")} - - %{ - method: :get, - url: "http://example.com/rel_me/link" - } -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_link.html")} - - %{ - method: :get, - url: "http://example.com/rel_me/null" - } -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_null.html")} - end) - + setup_all do + Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) :ok end @@ -39,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 122787bc2..a3a50cbb1 100644 --- a/test/web/rich_media/aws_signed_url_test.exs +++ b/test/web/rich_media/aws_signed_url_test.exs @@ -60,7 +60,8 @@ defmodule Pleroma.Web.RichMedia.TTL.AwsSignedUrlTest do {:ok, cache_ttl} = Cachex.ttl(:rich_media_cache, url) # as there is delay in setting and pulling the data from cache we ignore 1 second - assert_in_delta(valid_till * 1000, cache_ttl, 1000) + # make it 2 seconds for flakyness + assert_in_delta(valid_till * 1000, cache_ttl, 2000) end defp construct_s3_url(timestamp, valid_till) do diff --git a/test/web/rich_media/helpers_test.exs b/test/web/rich_media/helpers_test.exs index 92198f3d9..48884319d 100644 --- a/test/web/rich_media/helpers_test.exs +++ b/test/web/rich_media/helpers_test.exs @@ -15,12 +15,12 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do setup do mock(fn env -> apply(HttpRequestMock, :request, [env]) end) - rich_media = Config.get([:rich_media, :enabled]) - on_exit(fn -> Config.put([:rich_media, :enabled], rich_media) end) :ok end + clear_config([:rich_media, :enabled]) + test "refuses to crawl incomplete URLs" do user = insert(:user) diff --git a/test/web/rich_media/parser_test.exs b/test/web/rich_media/parser_test.exs index 19c19e895..b75bdf96f 100644 --- a/test/web/rich_media/parser_test.exs +++ b/test/web/rich_media/parser_test.exs @@ -59,7 +59,8 @@ defmodule Pleroma.Web.RichMedia.ParserTest do test "doesn't just add a title" do assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/non-ogp") == - {:error, "Found metadata was invalid or incomplete: %{}"} + {:error, + "Found metadata was invalid or incomplete: %{url: \"http://example.com/non-ogp\"}"} end test "parses ogp" do @@ -71,7 +72,7 @@ defmodule Pleroma.Web.RichMedia.ParserTest do description: "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.", type: "video.movie", - url: "http://www.imdb.com/title/tt0117500/" + url: "http://example.com/ogp" }} end @@ -84,7 +85,7 @@ defmodule Pleroma.Web.RichMedia.ParserTest do description: "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.", type: "video.movie", - url: "http://www.imdb.com/title/tt0117500/" + url: "http://example.com/ogp-missing-title" }} end @@ -96,7 +97,8 @@ defmodule Pleroma.Web.RichMedia.ParserTest do site: "@flickr", image: "https://farm6.staticflickr.com/5510/14338202952_93595258ff_z.jpg", title: "Small Island Developing States Photo Submission", - description: "View the album on Flickr." + description: "View the album on Flickr.", + url: "http://example.com/twitter-card" }} end @@ -120,7 +122,7 @@ defmodule Pleroma.Web.RichMedia.ParserTest do thumbnail_width: 150, title: "Bacon Lollys", type: "photo", - url: "https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg", + url: "http://example.com/oembed", version: "1.0", web_page: "https://www.flickr.com/photos/bees/2362225867/", web_page_short_url: "https://flic.kr/p/4AK2sc", diff --git a/test/web/rich_media/parsers/twitter_card_test.exs b/test/web/rich_media/parsers/twitter_card_test.exs new file mode 100644 index 000000000..f8e1c9b40 --- /dev/null +++ b/test/web/rich_media/parsers/twitter_card_test.exs @@ -0,0 +1,69 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do + use ExUnit.Case, async: true + 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"} + end + + test "parses twitter card with only name attributes" do + html = File.read!("test/fixtures/nypd-facial-recognition-children-teenagers3.html") + + assert TwitterCard.parse(html, %{}) == + {:ok, + %{ + "app:id:googleplay": "com.nytimes.android", + "app:name:googleplay": "NYTimes", + "app:url:googleplay": "nytimes://reader/id/100000006583622", + site: nil, + title: + "She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. - The New York Times" + }} + end + + test "parses twitter card with only property attributes" do + html = File.read!("test/fixtures/nypd-facial-recognition-children-teenagers2.html") + + assert TwitterCard.parse(html, %{}) == + {:ok, + %{ + card: "summary_large_image", + description: + "With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.", + image: + "https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-videoSixteenByNineJumbo1600.jpg", + "image:alt": "", + title: + "She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.", + url: + "https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html" + }} + end + + test "parses twitter card with name & property attributes" do + html = File.read!("test/fixtures/nypd-facial-recognition-children-teenagers.html") + + assert TwitterCard.parse(html, %{}) == + {:ok, + %{ + "app:id:googleplay": "com.nytimes.android", + "app:name:googleplay": "NYTimes", + "app:url:googleplay": "nytimes://reader/id/100000006583622", + card: "summary_large_image", + description: + "With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.", + image: + "https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-videoSixteenByNineJumbo1600.jpg", + "image:alt": "", + site: nil, + title: + "She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.", + url: + "https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html" + }} + 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 new file mode 100644 index 000000000..3d52c00e4 --- /dev/null +++ b/test/web/streamer/ping_test.exs @@ -0,0 +1,36 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PingTest do + use Pleroma.DataCase + + import Pleroma.Factory + alias Pleroma.Web.Streamer + + setup do + start_supervised({Streamer.supervisor(), [ping_interval: 30]}) + + :ok + end + + describe "sockets" do + setup do + user = insert(:user) + {:ok, %{user: user}} + end + + test "it sends pings", %{user: user} do + task = + Task.async(fn -> + assert_receive {:text, received_event}, 40 + assert_receive {:text, received_event}, 40 + assert_receive {:text, received_event}, 40 + end) + + Streamer.add_socket("public", %{transport_pid: task.pid, assigns: %{user: user}}) + + Task.await(task) + end + end +end diff --git a/test/web/streamer/state_test.exs b/test/web/streamer/state_test.exs new file mode 100644 index 000000000..d1aeac541 --- /dev/null +++ b/test/web/streamer/state_test.exs @@ -0,0 +1,54 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.StateTest do + use Pleroma.DataCase + + import Pleroma.Factory + alias Pleroma.Web.Streamer + alias Pleroma.Web.Streamer.StreamerSocket + + @moduletag needs_streamer: true + + describe "sockets" do + setup do + user = insert(:user) + user2 = insert(:user) + {:ok, %{user: user, user2: user2}} + end + + test "it can add a socket", %{user: user} do + Streamer.add_socket("public", %{transport_pid: 1, assigns: %{user: user}}) + + assert(%{"public" => [%StreamerSocket{transport_pid: 1}]} = Streamer.get_sockets()) + end + + test "it can add multiple sockets per user", %{user: user} do + Streamer.add_socket("public", %{transport_pid: 1, assigns: %{user: user}}) + Streamer.add_socket("public", %{transport_pid: 2, assigns: %{user: user}}) + + assert( + %{ + "public" => [ + %StreamerSocket{transport_pid: 2}, + %StreamerSocket{transport_pid: 1} + ] + } = Streamer.get_sockets() + ) + end + + test "it will not add a duplicate socket", %{user: user} do + Streamer.add_socket("activity", %{transport_pid: 1, assigns: %{user: user}}) + Streamer.add_socket("activity", %{transport_pid: 1, assigns: %{user: user}}) + + assert( + %{ + "activity" => [ + %StreamerSocket{transport_pid: 1} + ] + } = Streamer.get_sockets() + ) + end + end +end diff --git a/test/web/streamer_test.exs b/test/web/streamer/streamer_test.exs index 4633d7765..7166d6f0b 100644 --- a/test/web/streamer_test.exs +++ b/test/web/streamer/streamer_test.exs @@ -1,36 +1,29 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.StreamerTest do use Pleroma.DataCase + import Pleroma.Factory + + alias Pleroma.Conversation.Participation alias Pleroma.List alias Pleroma.User alias Pleroma.Web.CommonAPI alias Pleroma.Web.Streamer - import Pleroma.Factory + alias Pleroma.Web.Streamer.StreamerSocket + alias Pleroma.Web.Streamer.Worker - setup do - skip_thread_containment = Pleroma.Config.get([:instance, :skip_thread_containment]) + @moduletag needs_streamer: true, capture_log: true - on_exit(fn -> - Pleroma.Config.put([:instance, :skip_thread_containment], skip_thread_containment) - end) + @streamer_timeout 150 + @streamer_start_wait 10 - :ok - end + clear_config_all([:instance, :skip_thread_containment]) describe "user streams" do setup do - GenServer.start(Streamer, %{}, name: Streamer) - - on_exit(fn -> - if pid = Process.whereis(Streamer) do - Process.exit(pid, :kill) - end - end) - user = insert(:user) notify = insert(:notification, user: user, activity: build(:note_activity)) {:ok, %{user: user, notify: notify}} @@ -39,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( @@ -54,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( @@ -65,6 +58,83 @@ defmodule Pleroma.Web.StreamerTest do Streamer.stream("user:notification", notify) Task.await(task) end + + test "it doesn't send notify to the 'user:notification' stream when a user is blocked", %{ + user: user + } do + blocked = insert(:user) + {:ok, _user_relationship} = User.block(user, blocked) + + task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) + + Streamer.add_socket( + "user:notification", + %{transport_pid: task.pid, assigns: %{user: user}} + ) + + {:ok, activity} = CommonAPI.post(user, %{"status" => ":("}) + {:ok, notif, _} = CommonAPI.favorite(activity.id, blocked) + + Streamer.stream("user:notification", notif) + Task.await(task) + end + + test "it doesn't send notify to the 'user:notification' stream when a thread is muted", %{ + user: user + } do + user2 = insert(:user) + task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) + + Streamer.add_socket( + "user:notification", + %{transport_pid: task.pid, assigns: %{user: user}} + ) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) + {:ok, activity} = CommonAPI.add_mute(user, activity) + {:ok, notif, _} = CommonAPI.favorite(activity.id, user2) + Streamer.stream("user:notification", notif) + Task.await(task) + end + + test "it doesn't send notify to the 'user:notification' stream' when a domain is blocked", %{ + user: user + } do + user2 = insert(:user, %{ap_id: "https://hecking-lewd-place.com/user/meanie"}) + task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) + + Streamer.add_socket( + "user:notification", + %{transport_pid: task.pid, assigns: %{user: user}} + ) + + {:ok, user} = User.block_domain(user, "hecking-lewd-place.com") + {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) + {:ok, notif, _} = CommonAPI.favorite(activity.id, user2) + + Streamer.stream("user:notification", notif) + Task.await(task) + end + + test "it sends follow activities to the 'user:notification' stream", %{ + user: user + } do + user2 = insert(:user) + task = Task.async(fn -> assert_receive {:text, _}, @streamer_timeout end) + + Process.sleep(@streamer_start_wait) + + Streamer.add_socket( + "user:notification", + %{transport_pid: task.pid, assigns: %{user: user}} + ) + + {:ok, _follower, _followed, _activity} = CommonAPI.follow(user2, user) + + # We don't directly pipe the notification to the streamer as it's already + # generated as a side effect of CommonAPI.follow(). + Task.await(task) + end end test "it sends to public" do @@ -73,14 +143,12 @@ defmodule Pleroma.Web.StreamerTest do task = Task.async(fn -> - assert_receive {:text, _}, 4_000 + assert_receive {:text, _}, @streamer_timeout end) - fake_socket = %{ + fake_socket = %StreamerSocket{ transport_pid: task.pid, - assigns: %{ - user: user - } + user: user } {:ok, activity} = CommonAPI.post(other_user, %{"status" => "Test"}) @@ -89,7 +157,7 @@ defmodule Pleroma.Web.StreamerTest do "public" => [fake_socket] } - Streamer.push_to_socket(topics, "public", activity) + Worker.push_to_socket(topics, "public", activity) Task.await(task) @@ -102,15 +170,13 @@ 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) - fake_socket = %{ + fake_socket = %StreamerSocket{ transport_pid: task.pid, - assigns: %{ - user: user - } + user: user } {:ok, activity} = CommonAPI.delete(activity.id, other_user) @@ -119,7 +185,7 @@ defmodule Pleroma.Web.StreamerTest do "public" => [fake_socket] } - Streamer.push_to_socket(topics, "public", activity) + Worker.push_to_socket(topics, "public", activity) Task.await(task) end @@ -128,7 +194,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, @@ -140,9 +207,9 @@ defmodule Pleroma.Web.StreamerTest do ) task = Task.async(fn -> refute_receive {:text, _}, 1_000 end) - fake_socket = %{transport_pid: task.pid, assigns: %{user: user}} + fake_socket = %StreamerSocket{transport_pid: task.pid, user: user} topics = %{"public" => [fake_socket]} - Streamer.push_to_socket(topics, "public", activity) + Worker.push_to_socket(topics, "public", activity) Task.await(task) end @@ -150,7 +217,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, @@ -162,9 +230,9 @@ defmodule Pleroma.Web.StreamerTest do ) task = Task.async(fn -> assert_receive {:text, _}, 1_000 end) - fake_socket = %{transport_pid: task.pid, assigns: %{user: user}} + fake_socket = %StreamerSocket{transport_pid: task.pid, user: user} topics = %{"public" => [fake_socket]} - Streamer.push_to_socket(topics, "public", activity) + Worker.push_to_socket(topics, "public", activity) Task.await(task) end @@ -172,7 +240,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, @@ -184,40 +253,76 @@ defmodule Pleroma.Web.StreamerTest do ) task = Task.async(fn -> assert_receive {:text, _}, 1_000 end) - fake_socket = %{transport_pid: task.pid, assigns: %{user: user}} + fake_socket = %StreamerSocket{transport_pid: task.pid, user: user} topics = %{"public" => [fake_socket]} - Streamer.push_to_socket(topics, "public", activity) + Worker.push_to_socket(topics, "public", activity) Task.await(task) end end - test "it doesn't send to blocked users" do - user = insert(:user) - blocked_user = insert(:user) - {:ok, user} = User.block(user, blocked_user) + describe "blocks" do + test "it doesn't send messages involving blocked users" do + user = insert(:user) + blocked_user = insert(:user) + {:ok, _user_relationship} = User.block(user, blocked_user) - task = - Task.async(fn -> - refute_receive {:text, _}, 1_000 - end) + task = + Task.async(fn -> + refute_receive {:text, _}, 1_000 + end) - fake_socket = %{ - transport_pid: task.pid, - assigns: %{ + fake_socket = %StreamerSocket{ + transport_pid: task.pid, user: user } - } - {:ok, activity} = CommonAPI.post(blocked_user, %{"status" => "Test"}) + {:ok, activity} = CommonAPI.post(blocked_user, %{"status" => "Test"}) - topics = %{ - "public" => [fake_socket] - } + topics = %{ + "public" => [fake_socket] + } - Streamer.push_to_socket(topics, "public", activity) + Worker.push_to_socket(topics, "public", activity) - Task.await(task) + Task.await(task) + end + + test "it doesn't send messages transitively involving blocked users" do + blocker = insert(:user) + blockee = insert(:user) + friend = insert(:user) + + task = + Task.async(fn -> + refute_receive {:text, _}, 1_000 + end) + + fake_socket = %StreamerSocket{ + transport_pid: task.pid, + user: blocker + } + + topics = %{ + "public" => [fake_socket] + } + + {:ok, _user_relationship} = User.block(blocker, blockee) + + {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey! @#{blockee.nickname}"}) + + Worker.push_to_socket(topics, "public", activity_one) + + {:ok, activity_two} = CommonAPI.post(blockee, %{"status" => "hey! @#{friend.nickname}"}) + + Worker.push_to_socket(topics, "public", activity_two) + + {:ok, activity_three} = CommonAPI.post(blockee, %{"status" => "hey! @#{blocker.nickname}"}) + + Worker.push_to_socket(topics, "public", activity_three) + + Task.await(task) + end end test "it doesn't send unwanted DMs to list" do @@ -235,11 +340,9 @@ defmodule Pleroma.Web.StreamerTest do refute_receive {:text, _}, 1_000 end) - fake_socket = %{ + fake_socket = %StreamerSocket{ transport_pid: task.pid, - assigns: %{ - user: user_a - } + user: user_a } {:ok, activity} = @@ -252,7 +355,7 @@ defmodule Pleroma.Web.StreamerTest do "list:#{list.id}" => [fake_socket] } - Streamer.handle_cast(%{action: :stream, topic: "list", item: activity}, topics) + Worker.handle_call({:stream, "list", activity}, self(), topics) Task.await(task) end @@ -269,11 +372,9 @@ defmodule Pleroma.Web.StreamerTest do refute_receive {:text, _}, 1_000 end) - fake_socket = %{ + fake_socket = %StreamerSocket{ transport_pid: task.pid, - assigns: %{ - user: user_a - } + user: user_a } {:ok, activity} = @@ -286,12 +387,12 @@ defmodule Pleroma.Web.StreamerTest do "list:#{list.id}" => [fake_socket] } - Streamer.handle_cast(%{action: :stream, topic: "list", item: activity}, topics) + Worker.handle_call({:stream, "list", activity}, self(), topics) Task.await(task) end - test "it send wanted private posts to list" do + test "it sends wanted private posts to list" do user_a = insert(:user) user_b = insert(:user) @@ -305,11 +406,9 @@ defmodule Pleroma.Web.StreamerTest do assert_receive {:text, _}, 1_000 end) - fake_socket = %{ + fake_socket = %StreamerSocket{ transport_pid: task.pid, - assigns: %{ - user: user_a - } + user: user_a } {:ok, activity} = @@ -318,11 +417,12 @@ defmodule Pleroma.Web.StreamerTest do "visibility" => "private" }) - topics = %{ - "list:#{list.id}" => [fake_socket] - } + Streamer.add_socket( + "list:#{list.id}", + fake_socket + ) - Streamer.handle_cast(%{action: :stream, topic: "list", item: activity}, topics) + Worker.handle_call({:stream, "list", activity}, self(), %{}) Task.await(task) end @@ -338,11 +438,9 @@ defmodule Pleroma.Web.StreamerTest do refute_receive {:text, _}, 1_000 end) - fake_socket = %{ + fake_socket = %StreamerSocket{ transport_pid: task.pid, - assigns: %{ - user: user1 - } + user: user1 } {:ok, create_activity} = CommonAPI.post(user3, %{"status" => "I'm kawen"}) @@ -352,21 +450,33 @@ defmodule Pleroma.Web.StreamerTest do "public" => [fake_socket] } - Streamer.push_to_socket(topics, "public", announce_activity) + Worker.push_to_socket(topics, "public", announce_activity) Task.await(task) end - describe "direct streams" do - setup do - GenServer.start(Streamer, %{}, name: Streamer) + test "it doesn't send posts from muted threads" do + user = insert(:user) + user2 = insert(:user) + {:ok, user2, user, _activity} = CommonAPI.follow(user2, user) - on_exit(fn -> - if pid = Process.whereis(Streamer) do - Process.exit(pid, :kill) - end - end) + {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) + + {:ok, activity} = CommonAPI.add_mute(user2, activity) + task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) + + Streamer.add_socket( + "user", + %{transport_pid: task.pid, assigns: %{user: user2}} + ) + + Streamer.stream("user", activity) + Task.await(task) + end + + describe "direct streams" do + setup do :ok end @@ -376,7 +486,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( @@ -393,7 +510,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) @@ -405,12 +522,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" => "delete", "payload" => _} = Jason.decode!(received_event) - refute_receive {:text, _}, 4_000 + refute_receive {:text, _}, @streamer_timeout end) + Process.sleep(@streamer_start_wait) + Streamer.add_socket( "direct", %{transport_pid: task.pid, assigns: %{user: user}} @@ -440,10 +559,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) @@ -452,6 +571,8 @@ defmodule Pleroma.Web.StreamerTest do assert last_status["id"] == to_string(create_activity.id) end) + Process.sleep(@streamer_start_wait) + Streamer.add_socket( "direct", %{transport_pid: task.pid, assigns: %{user: user}} diff --git a/test/web/twitter_api/password_controller_test.exs b/test/web/twitter_api/password_controller_test.exs index 3a7246ea8..29ba7d265 100644 --- a/test/web/twitter_api/password_controller_test.exs +++ b/test/web/twitter_api/password_controller_test.exs @@ -6,6 +6,7 @@ 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..444949375 --- /dev/null +++ b/test/web/twitter_api/remote_follow_controller_test.exs @@ -0,0 +1,235 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 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) + + response = + 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}}) + |> response(200) + + assert response =~ "Account followed!" + assert user2.follower_address in User.following(user) + 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) + + response = + 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}}) + |> response(200) + + assert response =~ "Account followed!" + end + end + + describe "POST /ostatus_subscribe - follow/2 without assigned user " do + test "follows", %{conn: conn} do + user = insert(:user) + user2 = insert(:user) + + response = + conn + |> post(remote_follow_path(conn, :do_follow), %{ + "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} + }) + |> response(200) + + assert response =~ "Account followed!" + 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/representers/object_representer_test.exs b/test/web/twitter_api/representers/object_representer_test.exs deleted file mode 100644 index c3cf330f1..000000000 --- a/test/web/twitter_api/representers/object_representer_test.exs +++ /dev/null @@ -1,60 +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.TwitterAPI.Representers.ObjectReprenterTest do - use Pleroma.DataCase - - alias Pleroma.Object - alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter - - test "represent an image attachment" do - object = %Object{ - id: 5, - data: %{ - "type" => "Image", - "url" => [ - %{ - "mediaType" => "sometype", - "href" => "someurl" - } - ], - "uuid" => 6 - } - } - - expected_object = %{ - id: 6, - url: "someurl", - mimetype: "sometype", - oembed: false, - description: nil - } - - assert expected_object == ObjectRepresenter.to_map(object) - end - - test "represents mastodon-style attachments" do - object = %Object{ - id: nil, - data: %{ - "mediaType" => "image/png", - "name" => "blabla", - "type" => "Document", - "url" => - "http://mastodon.example.org/system/media_attachments/files/000/000/001/original/8619f31c6edec470.png" - } - } - - expected_object = %{ - url: - "http://mastodon.example.org/system/media_attachments/files/000/000/001/original/8619f31c6edec470.png", - mimetype: "image/png", - oembed: false, - id: nil, - description: "blabla" - } - - assert expected_object == ObjectRepresenter.to_map(object) - end -end diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/web/twitter_api/twitter_api_controller_test.exs deleted file mode 100644 index 8bb8aa36d..000000000 --- a/test/web/twitter_api/twitter_api_controller_test.exs +++ /dev/null @@ -1,2159 +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.TwitterAPI.ControllerTest do - use Pleroma.Web.ConnCase - alias Comeonin.Pbkdf2 - alias Ecto.Changeset - alias Pleroma.Activity - alias Pleroma.Builders.ActivityBuilder - alias Pleroma.Builders.UserBuilder - alias Pleroma.Notification - alias Pleroma.Object - alias Pleroma.Repo - alias Pleroma.User - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.CommonAPI - alias Pleroma.Web.OAuth.Token - alias Pleroma.Web.TwitterAPI.ActivityView - alias Pleroma.Web.TwitterAPI.Controller - alias Pleroma.Web.TwitterAPI.NotificationView - alias Pleroma.Web.TwitterAPI.TwitterAPI - alias Pleroma.Web.TwitterAPI.UserView - - import Mock - import Pleroma.Factory - import Swoosh.TestAssertions - - @banner "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7" - - describe "POST /api/account/update_profile_banner" do - test "it updates the banner", %{conn: conn} do - user = insert(:user) - - conn - |> assign(:user, user) - |> post(authenticated_twitter_api__path(conn, :update_banner), %{"banner" => @banner}) - |> json_response(200) - - user = refresh_record(user) - assert user.info.banner["type"] == "Image" - end - - test "profile banner can be reset", %{conn: conn} do - user = insert(:user) - - conn - |> assign(:user, user) - |> post(authenticated_twitter_api__path(conn, :update_banner), %{"banner" => ""}) - |> json_response(200) - - user = refresh_record(user) - assert user.info.banner == %{} - end - end - - describe "POST /api/qvitter/update_background_image" do - test "it updates the background", %{conn: conn} do - user = insert(:user) - - conn - |> assign(:user, user) - |> post(authenticated_twitter_api__path(conn, :update_background), %{"img" => @banner}) - |> json_response(200) - - user = refresh_record(user) - assert user.info.background["type"] == "Image" - end - - test "background can be reset", %{conn: conn} do - user = insert(:user) - - conn - |> assign(:user, user) - |> post(authenticated_twitter_api__path(conn, :update_background), %{"img" => ""}) - |> json_response(200) - - user = refresh_record(user) - assert user.info.background == %{} - end - end - - describe "POST /api/account/verify_credentials" do - setup [:valid_user] - - test "without valid credentials", %{conn: conn} do - conn = post(conn, "/api/account/verify_credentials.json") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials", %{conn: conn, user: user} do - response = - conn - |> with_credentials(user.nickname, "test") - |> post("/api/account/verify_credentials.json") - |> json_response(200) - - assert response == - UserView.render("show.json", %{user: user, token: response["token"], for: user}) - end - end - - describe "POST /statuses/update.json" do - setup [:valid_user] - - test "without valid credentials", %{conn: conn} do - conn = post(conn, "/api/statuses/update.json") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials", %{conn: conn, user: user} do - conn_with_creds = conn |> with_credentials(user.nickname, "test") - request_path = "/api/statuses/update.json" - - error_response = %{ - "request" => request_path, - "error" => "Client must provide a 'status' parameter with a value." - } - - conn = - conn_with_creds - |> post(request_path) - - assert json_response(conn, 400) == error_response - - conn = - conn_with_creds - |> post(request_path, %{status: ""}) - - assert json_response(conn, 400) == error_response - - conn = - conn_with_creds - |> post(request_path, %{status: " "}) - - assert json_response(conn, 400) == error_response - - # we post with visibility private in order to avoid triggering relay - conn = - conn_with_creds - |> post(request_path, %{status: "Nice meme.", visibility: "private"}) - - assert json_response(conn, 200) == - ActivityView.render("activity.json", %{ - activity: Repo.one(Activity), - user: user, - for: user - }) - end - end - - describe "GET /statuses/public_timeline.json" do - setup [:valid_user] - - test "returns statuses", %{conn: conn} do - user = insert(:user) - activities = ActivityBuilder.insert_list(30, %{}, %{user: user}) - ActivityBuilder.insert_list(10, %{}, %{user: user}) - since_id = List.last(activities).id - - conn = - conn - |> get("/api/statuses/public_timeline.json", %{since_id: since_id}) - - response = json_response(conn, 200) - - assert length(response) == 10 - end - - test "returns 403 to unauthenticated request when the instance is not public", %{conn: conn} do - Pleroma.Config.put([:instance, :public], false) - - conn - |> get("/api/statuses/public_timeline.json") - |> json_response(403) - - Pleroma.Config.put([:instance, :public], true) - end - - test "returns 200 to authenticated request when the instance is not public", - %{conn: conn, user: user} do - Pleroma.Config.put([:instance, :public], false) - - conn - |> with_credentials(user.nickname, "test") - |> get("/api/statuses/public_timeline.json") - |> json_response(200) - - Pleroma.Config.put([:instance, :public], true) - end - - test "returns 200 to unauthenticated request when the instance is public", %{conn: conn} do - conn - |> get("/api/statuses/public_timeline.json") - |> json_response(200) - end - - test "returns 200 to authenticated request when the instance is public", - %{conn: conn, user: user} do - conn - |> with_credentials(user.nickname, "test") - |> get("/api/statuses/public_timeline.json") - |> json_response(200) - end - - test_with_mock "treats user as unauthenticated if `assigns[:token]` is present but lacks `read` permission", - Controller, - [:passthrough], - [] do - token = insert(:oauth_token, scopes: ["write"]) - - build_conn() - |> put_req_header("authorization", "Bearer #{token.token}") - |> get("/api/statuses/public_timeline.json") - |> json_response(200) - - assert called(Controller.public_timeline(%{assigns: %{user: nil}}, :_)) - end - end - - describe "GET /statuses/public_and_external_timeline.json" do - setup [:valid_user] - - test "returns 403 to unauthenticated request when the instance is not public", %{conn: conn} do - Pleroma.Config.put([:instance, :public], false) - - conn - |> get("/api/statuses/public_and_external_timeline.json") - |> json_response(403) - - Pleroma.Config.put([:instance, :public], true) - end - - test "returns 200 to authenticated request when the instance is not public", - %{conn: conn, user: user} do - Pleroma.Config.put([:instance, :public], false) - - conn - |> with_credentials(user.nickname, "test") - |> get("/api/statuses/public_and_external_timeline.json") - |> json_response(200) - - Pleroma.Config.put([:instance, :public], true) - end - - test "returns 200 to unauthenticated request when the instance is public", %{conn: conn} do - conn - |> get("/api/statuses/public_and_external_timeline.json") - |> json_response(200) - end - - test "returns 200 to authenticated request when the instance is public", - %{conn: conn, user: user} do - conn - |> with_credentials(user.nickname, "test") - |> get("/api/statuses/public_and_external_timeline.json") - |> json_response(200) - end - end - - describe "GET /statuses/show/:id.json" do - test "returns one status", %{conn: conn} do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey!"}) - actor = User.get_cached_by_ap_id(activity.data["actor"]) - - conn = - conn - |> get("/api/statuses/show/#{activity.id}.json") - - response = json_response(conn, 200) - - assert response == ActivityView.render("activity.json", %{activity: activity, user: actor}) - end - end - - describe "GET /users/show.json" do - test "gets user with screen_name", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> get("/api/users/show.json", %{"screen_name" => user.nickname}) - - response = json_response(conn, 200) - - assert response["id"] == user.id - end - - test "gets user with user_id", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> get("/api/users/show.json", %{"user_id" => user.id}) - - response = json_response(conn, 200) - - assert response["id"] == user.id - end - - test "gets a user for a logged in user", %{conn: conn} do - user = insert(:user) - logged_in = insert(:user) - - {:ok, logged_in, user, _activity} = TwitterAPI.follow(logged_in, %{"user_id" => user.id}) - - conn = - conn - |> with_credentials(logged_in.nickname, "test") - |> get("/api/users/show.json", %{"user_id" => user.id}) - - response = json_response(conn, 200) - - assert response["following"] == true - end - end - - describe "GET /statusnet/conversation/:id.json" do - test "returns the statuses in the conversation", %{conn: conn} do - {:ok, _user} = UserBuilder.insert() - {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"}) - {:ok, _activity_two} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"}) - {:ok, _activity_three} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"}) - - conn = - conn - |> get("/api/statusnet/conversation/#{activity.data["context_id"]}.json") - - response = json_response(conn, 200) - - assert length(response) == 2 - end - end - - describe "GET /statuses/friends_timeline.json" do - setup [:valid_user] - - test "without valid credentials", %{conn: conn} do - conn = get(conn, "/api/statuses/friends_timeline.json") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials", %{conn: conn, user: current_user} do - user = insert(:user) - - activities = - ActivityBuilder.insert_list(30, %{"to" => [User.ap_followers(user)]}, %{user: user}) - - returned_activities = - ActivityBuilder.insert_list(10, %{"to" => [User.ap_followers(user)]}, %{user: user}) - - other_user = insert(:user) - ActivityBuilder.insert_list(10, %{}, %{user: other_user}) - since_id = List.last(activities).id - - current_user = - Changeset.change(current_user, following: [User.ap_followers(user)]) - |> Repo.update!() - - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> get("/api/statuses/friends_timeline.json", %{since_id: since_id}) - - response = json_response(conn, 200) - - assert length(response) == 10 - - assert response == - Enum.map(returned_activities, fn activity -> - ActivityView.render("activity.json", %{ - activity: activity, - user: User.get_cached_by_ap_id(activity.data["actor"]), - for: current_user - }) - end) - end - end - - describe "GET /statuses/dm_timeline.json" do - test "it show direct messages", %{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, direct_two} = - CommonAPI.post(user_two, %{ - "status" => "Hi @#{user_one.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/statuses/dm_timeline.json") - - [status, status_two] = json_response(res_conn, 200) - assert status["id"] == direct_two.id - assert status_two["id"] == direct.id - 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, blocker) - |> get("/api/statuses/dm_timeline.json") - - [status] = json_response(res_conn, 200) - assert status["id"] == direct.id - end - end - - describe "GET /statuses/mentions.json" do - setup [:valid_user] - - test "without valid credentials", %{conn: conn} do - conn = get(conn, "/api/statuses/mentions.json") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials", %{conn: conn, user: current_user} do - {:ok, activity} = - CommonAPI.post(current_user, %{ - "status" => "why is tenshi eating a corndog so cute?", - "visibility" => "public" - }) - - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> get("/api/statuses/mentions.json") - - response = json_response(conn, 200) - - assert length(response) == 1 - - assert Enum.at(response, 0) == - ActivityView.render("activity.json", %{ - user: current_user, - for: current_user, - activity: activity - }) - end - - test "does not show DMs in mentions timeline", %{conn: conn, user: current_user} do - {:ok, _activity} = - CommonAPI.post(current_user, %{ - "status" => "Have you guys ever seen how cute tenshi eating a corndog is?", - "visibility" => "direct" - }) - - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> get("/api/statuses/mentions.json") - - response = json_response(conn, 200) - - assert Enum.empty?(response) - end - end - - describe "GET /api/qvitter/statuses/notifications.json" do - setup [:valid_user] - - test "without valid credentials", %{conn: conn} do - conn = get(conn, "/api/qvitter/statuses/notifications.json") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials", %{conn: conn, user: current_user} do - other_user = insert(:user) - - {:ok, _activity} = - ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: other_user}) - - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> get("/api/qvitter/statuses/notifications.json") - - response = json_response(conn, 200) - - assert length(response) == 1 - - assert response == - NotificationView.render("notification.json", %{ - notifications: Notification.for_user(current_user), - for: current_user - }) - end - - test "muted user", %{conn: conn, user: current_user} do - other_user = insert(:user) - - {:ok, current_user} = User.mute(current_user, other_user) - - {:ok, _activity} = - ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: other_user}) - - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> get("/api/qvitter/statuses/notifications.json") - - assert json_response(conn, 200) == [] - end - - test "muted user with with_muted parameter", %{conn: conn, user: current_user} do - other_user = insert(:user) - - {:ok, current_user} = User.mute(current_user, other_user) - - {:ok, _activity} = - ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: other_user}) - - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> get("/api/qvitter/statuses/notifications.json", %{"with_muted" => "true"}) - - assert length(json_response(conn, 200)) == 1 - end - end - - describe "POST /api/qvitter/statuses/notifications/read" do - setup [:valid_user] - - 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", %{conn: conn, user: current_user} do - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> 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", %{conn: conn, user: current_user} do - other_user = insert(:user) - - {:ok, _activity} = - ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: other_user}) - - response_conn = - conn - |> with_credentials(current_user.nickname, "test") - |> get("/api/qvitter/statuses/notifications.json") - - [notification] = response = json_response(response_conn, 200) - - assert length(response) == 1 - - assert notification["is_seen"] == 0 - - response_conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/qvitter/statuses/notifications/read", %{"latest_id" => notification["id"]}) - - [notification] = response = json_response(response_conn, 200) - - assert length(response) == 1 - - assert notification["is_seen"] == 1 - end - end - - describe "GET /statuses/user_timeline.json" do - setup [:valid_user] - - test "without any params", %{conn: conn} do - conn = get(conn, "/api/statuses/user_timeline.json") - - assert json_response(conn, 400) == %{ - "error" => "You need to specify screen_name or user_id", - "request" => "/api/statuses/user_timeline.json" - } - end - - test "with user_id", %{conn: conn} do - user = insert(:user) - {:ok, activity} = ActivityBuilder.insert(%{"id" => 1}, %{user: user}) - - conn = get(conn, "/api/statuses/user_timeline.json", %{"user_id" => user.id}) - response = json_response(conn, 200) - assert length(response) == 1 - - assert Enum.at(response, 0) == - ActivityView.render("activity.json", %{user: user, activity: activity}) - end - - test "with screen_name", %{conn: conn} do - user = insert(:user) - {:ok, activity} = ActivityBuilder.insert(%{"id" => 1}, %{user: user}) - - conn = get(conn, "/api/statuses/user_timeline.json", %{"screen_name" => user.nickname}) - response = json_response(conn, 200) - assert length(response) == 1 - - assert Enum.at(response, 0) == - ActivityView.render("activity.json", %{user: user, activity: activity}) - end - - test "with credentials", %{conn: conn, user: current_user} do - {:ok, activity} = ActivityBuilder.insert(%{"id" => 1}, %{user: current_user}) - - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> get("/api/statuses/user_timeline.json") - - response = json_response(conn, 200) - - assert length(response) == 1 - - assert Enum.at(response, 0) == - ActivityView.render("activity.json", %{ - user: current_user, - for: current_user, - activity: activity - }) - end - - test "with credentials with user_id", %{conn: conn, user: current_user} do - user = insert(:user) - {:ok, activity} = ActivityBuilder.insert(%{"id" => 1}, %{user: user}) - - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> get("/api/statuses/user_timeline.json", %{"user_id" => user.id}) - - response = json_response(conn, 200) - - assert length(response) == 1 - - assert Enum.at(response, 0) == - ActivityView.render("activity.json", %{user: user, activity: activity}) - end - - test "with credentials screen_name", %{conn: conn, user: current_user} do - user = insert(:user) - {:ok, activity} = ActivityBuilder.insert(%{"id" => 1}, %{user: user}) - - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> get("/api/statuses/user_timeline.json", %{"screen_name" => user.nickname}) - - response = json_response(conn, 200) - - assert length(response) == 1 - - assert Enum.at(response, 0) == - ActivityView.render("activity.json", %{user: user, activity: activity}) - end - - test "with credentials with user_id, excluding RTs", %{conn: conn, user: current_user} do - user = insert(:user) - {:ok, activity} = ActivityBuilder.insert(%{"id" => 1, "type" => "Create"}, %{user: user}) - {:ok, _} = ActivityBuilder.insert(%{"id" => 2, "type" => "Announce"}, %{user: user}) - - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> get("/api/statuses/user_timeline.json", %{ - "user_id" => user.id, - "include_rts" => "false" - }) - - response = json_response(conn, 200) - - assert length(response) == 1 - - assert Enum.at(response, 0) == - ActivityView.render("activity.json", %{user: user, activity: activity}) - - conn = - conn - |> get("/api/statuses/user_timeline.json", %{"user_id" => user.id, "include_rts" => "0"}) - - response = json_response(conn, 200) - - assert length(response) == 1 - - assert Enum.at(response, 0) == - ActivityView.render("activity.json", %{user: user, activity: activity}) - end - end - - describe "POST /friendships/create.json" do - setup [:valid_user] - - test "without valid credentials", %{conn: conn} do - conn = post(conn, "/api/friendships/create.json") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials", %{conn: conn, user: current_user} do - followed = insert(:user) - - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/friendships/create.json", %{user_id: followed.id}) - - current_user = User.get_cached_by_id(current_user.id) - assert User.ap_followers(followed) in current_user.following - - assert json_response(conn, 200) == - UserView.render("show.json", %{user: followed, for: current_user}) - end - - test "for restricted account", %{conn: conn, user: current_user} do - followed = insert(:user, info: %User.Info{locked: true}) - - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/friendships/create.json", %{user_id: followed.id}) - - current_user = User.get_cached_by_id(current_user.id) - followed = User.get_cached_by_id(followed.id) - - refute User.ap_followers(followed) in current_user.following - - assert json_response(conn, 200) == - UserView.render("show.json", %{user: followed, for: current_user}) - end - end - - describe "POST /friendships/destroy.json" do - setup [:valid_user] - - test "without valid credentials", %{conn: conn} do - conn = post(conn, "/api/friendships/destroy.json") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials", %{conn: conn, user: current_user} do - followed = insert(:user) - - {:ok, current_user} = User.follow(current_user, followed) - assert User.ap_followers(followed) in current_user.following - ActivityPub.follow(current_user, followed) - - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/friendships/destroy.json", %{user_id: followed.id}) - - current_user = User.get_cached_by_id(current_user.id) - assert current_user.following == [current_user.ap_id] - - assert json_response(conn, 200) == - UserView.render("show.json", %{user: followed, for: current_user}) - end - end - - describe "POST /blocks/create.json" do - setup [:valid_user] - - test "without valid credentials", %{conn: conn} do - conn = post(conn, "/api/blocks/create.json") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials", %{conn: conn, user: current_user} do - blocked = insert(:user) - - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/blocks/create.json", %{user_id: blocked.id}) - - current_user = User.get_cached_by_id(current_user.id) - assert User.blocks?(current_user, blocked) - - assert json_response(conn, 200) == - UserView.render("show.json", %{user: blocked, for: current_user}) - end - end - - describe "POST /blocks/destroy.json" do - setup [:valid_user] - - test "without valid credentials", %{conn: conn} do - conn = post(conn, "/api/blocks/destroy.json") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials", %{conn: conn, user: current_user} do - blocked = insert(:user) - - {:ok, current_user, blocked} = TwitterAPI.block(current_user, %{"user_id" => blocked.id}) - assert User.blocks?(current_user, blocked) - - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/blocks/destroy.json", %{user_id: blocked.id}) - - current_user = User.get_cached_by_id(current_user.id) - assert current_user.info.blocks == [] - - assert json_response(conn, 200) == - UserView.render("show.json", %{user: blocked, for: current_user}) - end - end - - describe "GET /help/test.json" do - test "returns \"ok\"", %{conn: conn} do - conn = get(conn, "/api/help/test.json") - assert json_response(conn, 200) == "ok" - end - end - - describe "POST /api/qvitter/update_avatar.json" do - setup [:valid_user] - - test "without valid credentials", %{conn: conn} do - conn = post(conn, "/api/qvitter/update_avatar.json") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials", %{conn: conn, user: current_user} do - avatar_image = File.read!("test/fixtures/avatar_data_uri") - - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/qvitter/update_avatar.json", %{img: avatar_image}) - - current_user = User.get_cached_by_id(current_user.id) - assert is_map(current_user.avatar) - - assert json_response(conn, 200) == - UserView.render("show.json", %{user: current_user, for: current_user}) - end - - test "user avatar can be reset", %{conn: conn, user: current_user} do - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/qvitter/update_avatar.json", %{img: ""}) - - current_user = User.get_cached_by_id(current_user.id) - assert current_user.avatar == nil - - assert json_response(conn, 200) == - UserView.render("show.json", %{user: current_user, for: current_user}) - end - end - - describe "GET /api/qvitter/mutes.json" do - setup [:valid_user] - - test "unimplemented mutes without valid credentials", %{conn: conn} do - conn = get(conn, "/api/qvitter/mutes.json") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "unimplemented mutes with credentials", %{conn: conn, user: current_user} do - response = - conn - |> with_credentials(current_user.nickname, "test") - |> get("/api/qvitter/mutes.json") - |> json_response(200) - - assert [] = response - end - end - - describe "POST /api/favorites/create/:id" do - setup [:valid_user] - - test "without valid credentials", %{conn: conn} do - note_activity = insert(:note_activity) - conn = post(conn, "/api/favorites/create/#{note_activity.id}.json") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials", %{conn: conn, user: current_user} do - note_activity = insert(:note_activity) - - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/favorites/create/#{note_activity.id}.json") - - assert json_response(conn, 200) - end - - test "with credentials, invalid param", %{conn: conn, user: current_user} do - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/favorites/create/wrong.json") - - assert json_response(conn, 400) - end - - test "with credentials, invalid activity", %{conn: conn, user: current_user} do - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/favorites/create/1.json") - - assert json_response(conn, 400) - end - end - - describe "POST /api/favorites/destroy/:id" do - setup [:valid_user] - - test "without valid credentials", %{conn: conn} do - note_activity = insert(:note_activity) - conn = post(conn, "/api/favorites/destroy/#{note_activity.id}.json") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials", %{conn: conn, user: current_user} do - note_activity = insert(:note_activity) - object = Object.normalize(note_activity) - ActivityPub.like(current_user, object) - - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/favorites/destroy/#{note_activity.id}.json") - - assert json_response(conn, 200) - end - end - - describe "POST /api/statuses/retweet/:id" do - setup [:valid_user] - - test "without valid credentials", %{conn: conn} do - note_activity = insert(:note_activity) - conn = post(conn, "/api/statuses/retweet/#{note_activity.id}.json") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials", %{conn: conn, user: current_user} do - note_activity = insert(:note_activity) - - request_path = "/api/statuses/retweet/#{note_activity.id}.json" - - response = - conn - |> with_credentials(current_user.nickname, "test") - |> post(request_path) - - activity = Activity.get_by_id(note_activity.id) - activity_user = User.get_cached_by_ap_id(note_activity.data["actor"]) - - assert json_response(response, 200) == - ActivityView.render("activity.json", %{ - user: activity_user, - for: current_user, - activity: activity - }) - end - end - - describe "POST /api/statuses/unretweet/:id" do - setup [:valid_user] - - test "without valid credentials", %{conn: conn} do - note_activity = insert(:note_activity) - conn = post(conn, "/api/statuses/unretweet/#{note_activity.id}.json") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials", %{conn: conn, user: current_user} do - note_activity = insert(:note_activity) - - request_path = "/api/statuses/retweet/#{note_activity.id}.json" - - _response = - conn - |> with_credentials(current_user.nickname, "test") - |> post(request_path) - - request_path = String.replace(request_path, "retweet", "unretweet") - - response = - conn - |> with_credentials(current_user.nickname, "test") - |> post(request_path) - - activity = Activity.get_by_id(note_activity.id) - activity_user = User.get_cached_by_ap_id(note_activity.data["actor"]) - - assert json_response(response, 200) == - ActivityView.render("activity.json", %{ - user: activity_user, - for: current_user, - activity: activity - }) - end - end - - describe "POST /api/account/register" do - test "it creates a new user", %{conn: conn} do - data = %{ - "nickname" => "lain", - "email" => "lain@wired.jp", - "fullname" => "lain iwakura", - "bio" => "close the world.", - "password" => "bear", - "confirm" => "bear" - } - - conn = - conn - |> post("/api/account/register", data) - - user = json_response(conn, 200) - - fetched_user = User.get_cached_by_nickname("lain") - assert user == UserView.render("show.json", %{user: fetched_user}) - end - - test "it returns errors on a problem", %{conn: conn} do - data = %{ - "email" => "lain@wired.jp", - "fullname" => "lain iwakura", - "bio" => "close the world.", - "password" => "bear", - "confirm" => "bear" - } - - conn = - conn - |> post("/api/account/register", data) - - errors = json_response(conn, 400) - - assert is_binary(errors["error"]) - end - end - - describe "POST /api/account/password_reset, with valid parameters" do - setup %{conn: conn} do - user = insert(:user) - conn = post(conn, "/api/account/password_reset?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 = Pleroma.Config.get([:instance, :notify_email]) - instance_name = Pleroma.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 /api/account/password_reset, with invalid parameters" do - setup [:valid_user] - - test "it returns 404 when user is not found", %{conn: conn, user: user} do - conn = post(conn, "/api/account/password_reset?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, "/api/account/password_reset?email=#{user.email}") - assert conn.status == 400 - assert conn.resp_body == "" - end - end - - describe "GET /api/account/confirm_email/:id/:token" 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 - - test "it redirects to root url", %{conn: conn, user: user} do - conn = get(conn, "/api/account/confirm_email/#{user.id}/#{user.info.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.info.confirmation_token}") - - user = User.get_cached_by_id(user.id) - - refute user.info.confirmation_pending - refute user.info.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.info.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 "POST /api/account/resend_confirmation_email" do - setup do - setting = Pleroma.Config.get([:instance, :account_activation_required]) - - unless setting do - Pleroma.Config.put([:instance, :account_activation_required], true) - on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end) - end - - 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 - - test "it returns 204 No Content", %{conn: conn, user: user} do - conn - |> assign(:user, user) - |> post("/api/account/resend_confirmation_email?email=#{user.email}") - |> json_response(:no_content) - end - - test "it sends confirmation email", %{conn: conn, user: user} do - conn - |> assign(:user, user) - |> post("/api/account/resend_confirmation_email?email=#{user.email}") - - email = Pleroma.Emails.UserEmail.account_confirmation_email(user) - notify_email = Pleroma.Config.get([:instance, :notify_email]) - instance_name = Pleroma.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/externalprofile/show" do - test "it returns the user", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> get("/api/externalprofile/show", %{profileurl: other_user.ap_id}) - - assert json_response(conn, 200) == UserView.render("show.json", %{user: other_user}) - end - end - - describe "GET /api/statuses/followers" do - test "it returns a user's followers", %{conn: conn} do - user = insert(:user) - follower_one = insert(:user) - follower_two = insert(:user) - _not_follower = insert(:user) - - {:ok, follower_one} = User.follow(follower_one, user) - {:ok, follower_two} = User.follow(follower_two, user) - - conn = - conn - |> assign(:user, user) - |> get("/api/statuses/followers") - - expected = UserView.render("index.json", %{users: [follower_one, follower_two], for: user}) - result = json_response(conn, 200) - assert Enum.sort(expected) == Enum.sort(result) - end - - test "it returns 20 followers per page", %{conn: conn} do - user = insert(:user) - followers = insert_list(21, :user) - - Enum.each(followers, fn follower -> - User.follow(follower, user) - end) - - res_conn = - conn - |> assign(:user, user) - |> get("/api/statuses/followers") - - result = json_response(res_conn, 200) - assert length(result) == 20 - - res_conn = - conn - |> assign(:user, user) - |> get("/api/statuses/followers?page=2") - - result = json_response(res_conn, 200) - assert length(result) == 1 - end - - test "it returns a given user's followers with user_id", %{conn: conn} do - user = insert(:user) - follower_one = insert(:user) - follower_two = insert(:user) - not_follower = insert(:user) - - {:ok, follower_one} = User.follow(follower_one, user) - {:ok, follower_two} = User.follow(follower_two, user) - - conn = - conn - |> assign(:user, not_follower) - |> get("/api/statuses/followers", %{"user_id" => user.id}) - - assert MapSet.equal?( - MapSet.new(json_response(conn, 200)), - MapSet.new( - UserView.render("index.json", %{ - users: [follower_one, follower_two], - for: not_follower - }) - ) - ) - end - - test "it returns empty when hide_followers is set to true", %{conn: conn} do - user = insert(:user, %{info: %{hide_followers: true}}) - follower_one = insert(:user) - follower_two = insert(:user) - not_follower = insert(:user) - - {:ok, _follower_one} = User.follow(follower_one, user) - {:ok, _follower_two} = User.follow(follower_two, user) - - response = - conn - |> assign(:user, not_follower) - |> get("/api/statuses/followers", %{"user_id" => user.id}) - |> json_response(200) - - assert [] == response - end - - test "it returns the followers when hide_followers is set to true if requested by the user themselves", - %{ - conn: conn - } do - user = insert(:user, %{info: %{hide_followers: true}}) - follower_one = insert(:user) - follower_two = insert(:user) - _not_follower = insert(:user) - - {:ok, _follower_one} = User.follow(follower_one, user) - {:ok, _follower_two} = User.follow(follower_two, user) - - conn = - conn - |> assign(:user, user) - |> get("/api/statuses/followers", %{"user_id" => user.id}) - - refute [] == json_response(conn, 200) - end - end - - describe "GET /api/statuses/blocks" do - test "it returns the list of users blocked by requester", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, user} = User.block(user, other_user) - - conn = - conn - |> assign(:user, user) - |> get("/api/statuses/blocks") - - expected = UserView.render("index.json", %{users: [other_user], for: user}) - result = json_response(conn, 200) - assert Enum.sort(expected) == Enum.sort(result) - end - end - - describe "GET /api/statuses/friends" do - test "it returns the logged in user's friends", %{conn: conn} do - user = insert(:user) - followed_one = insert(:user) - followed_two = insert(:user) - _not_followed = insert(:user) - - {:ok, user} = User.follow(user, followed_one) - {:ok, user} = User.follow(user, followed_two) - - conn = - conn - |> assign(:user, user) - |> get("/api/statuses/friends") - - expected = UserView.render("index.json", %{users: [followed_one, followed_two], for: user}) - result = json_response(conn, 200) - assert Enum.sort(expected) == Enum.sort(result) - end - - test "it returns 20 friends per page, except if 'export' is set to true", %{conn: conn} do - user = insert(:user) - followeds = insert_list(21, :user) - - {:ok, user} = - Enum.reduce(followeds, {:ok, user}, fn followed, {:ok, user} -> - User.follow(user, followed) - end) - - res_conn = - conn - |> assign(:user, user) - |> get("/api/statuses/friends") - - result = json_response(res_conn, 200) - assert length(result) == 20 - - res_conn = - conn - |> assign(:user, user) - |> get("/api/statuses/friends", %{page: 2}) - - result = json_response(res_conn, 200) - assert length(result) == 1 - - res_conn = - conn - |> assign(:user, user) - |> get("/api/statuses/friends", %{all: true}) - - result = json_response(res_conn, 200) - assert length(result) == 21 - end - - test "it returns a given user's friends with user_id", %{conn: conn} do - user = insert(:user) - followed_one = insert(:user) - followed_two = insert(:user) - _not_followed = insert(:user) - - {:ok, user} = User.follow(user, followed_one) - {:ok, user} = User.follow(user, followed_two) - - conn = - conn - |> assign(:user, user) - |> get("/api/statuses/friends", %{"user_id" => user.id}) - - assert MapSet.equal?( - MapSet.new(json_response(conn, 200)), - MapSet.new( - UserView.render("index.json", %{users: [followed_one, followed_two], for: user}) - ) - ) - end - - test "it returns empty when hide_follows is set to true", %{conn: conn} do - user = insert(:user, %{info: %{hide_follows: true}}) - followed_one = insert(:user) - followed_two = insert(:user) - not_followed = insert(:user) - - {:ok, user} = User.follow(user, followed_one) - {:ok, user} = User.follow(user, followed_two) - - conn = - conn - |> assign(:user, not_followed) - |> get("/api/statuses/friends", %{"user_id" => user.id}) - - assert [] == json_response(conn, 200) - end - - test "it returns friends when hide_follows is set to true if the user themselves request it", - %{ - conn: conn - } do - user = insert(:user, %{info: %{hide_follows: true}}) - followed_one = insert(:user) - followed_two = insert(:user) - _not_followed = insert(:user) - - {:ok, _user} = User.follow(user, followed_one) - {:ok, _user} = User.follow(user, followed_two) - - response = - conn - |> assign(:user, user) - |> get("/api/statuses/friends", %{"user_id" => user.id}) - |> json_response(200) - - refute [] == response - end - - test "it returns a given user's friends with screen_name", %{conn: conn} do - user = insert(:user) - followed_one = insert(:user) - followed_two = insert(:user) - _not_followed = insert(:user) - - {:ok, user} = User.follow(user, followed_one) - {:ok, user} = User.follow(user, followed_two) - - conn = - conn - |> assign(:user, user) - |> get("/api/statuses/friends", %{"screen_name" => user.nickname}) - - assert MapSet.equal?( - MapSet.new(json_response(conn, 200)), - MapSet.new( - UserView.render("index.json", %{users: [followed_one, followed_two], for: user}) - ) - ) - end - end - - describe "GET /friends/ids" do - test "it returns a user's friends", %{conn: conn} do - user = insert(:user) - followed_one = insert(:user) - followed_two = insert(:user) - _not_followed = insert(:user) - - {:ok, user} = User.follow(user, followed_one) - {:ok, user} = User.follow(user, followed_two) - - conn = - conn - |> assign(:user, user) - |> get("/api/friends/ids") - - expected = [followed_one.id, followed_two.id] - - assert MapSet.equal?( - MapSet.new(Poison.decode!(json_response(conn, 200))), - MapSet.new(expected) - ) - end - end - - describe "POST /api/account/update_profile.json" do - test "it updates a user's profile", %{conn: conn} do - user = insert(:user) - user2 = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/account/update_profile.json", %{ - "name" => "new name", - "description" => "hi @#{user2.nickname}" - }) - - user = Repo.get!(User, user.id) - assert user.name == "new name" - - assert user.bio == - "hi <span class='h-card'><a data-user='#{user2.id}' class='u-url mention' href='#{ - user2.ap_id - }'>@<span>#{user2.nickname}</span></a></span>" - - assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user}) - end - - test "it sets and un-sets hide_follows", %{conn: conn} do - user = insert(:user) - - conn - |> assign(:user, user) - |> post("/api/account/update_profile.json", %{ - "hide_follows" => "true" - }) - - user = Repo.get!(User, user.id) - assert user.info.hide_follows == true - - conn = - conn - |> assign(:user, user) - |> post("/api/account/update_profile.json", %{ - "hide_follows" => "false" - }) - - user = refresh_record(user) - assert user.info.hide_follows == false - assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user}) - end - - test "it sets and un-sets hide_followers", %{conn: conn} do - user = insert(:user) - - conn - |> assign(:user, user) - |> post("/api/account/update_profile.json", %{ - "hide_followers" => "true" - }) - - user = Repo.get!(User, user.id) - assert user.info.hide_followers == true - - conn = - conn - |> assign(:user, user) - |> post("/api/account/update_profile.json", %{ - "hide_followers" => "false" - }) - - user = Repo.get!(User, user.id) - assert user.info.hide_followers == false - assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user}) - end - - test "it sets and un-sets show_role", %{conn: conn} do - user = insert(:user) - - conn - |> assign(:user, user) - |> post("/api/account/update_profile.json", %{ - "show_role" => "true" - }) - - user = Repo.get!(User, user.id) - assert user.info.show_role == true - - conn = - conn - |> assign(:user, user) - |> post("/api/account/update_profile.json", %{ - "show_role" => "false" - }) - - user = Repo.get!(User, user.id) - assert user.info.show_role == false - assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user}) - end - - test "it sets and un-sets skip_thread_containment", %{conn: conn} do - user = insert(:user) - - response = - conn - |> assign(:user, user) - |> post("/api/account/update_profile.json", %{"skip_thread_containment" => "true"}) - |> json_response(200) - - assert response["pleroma"]["skip_thread_containment"] == true - user = refresh_record(user) - assert user.info.skip_thread_containment - - response = - conn - |> assign(:user, user) - |> post("/api/account/update_profile.json", %{"skip_thread_containment" => "false"}) - |> json_response(200) - - assert response["pleroma"]["skip_thread_containment"] == false - refute refresh_record(user).info.skip_thread_containment - end - - test "it locks an account", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/account/update_profile.json", %{ - "locked" => "true" - }) - - user = Repo.get!(User, user.id) - assert user.info.locked == true - - assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user}) - end - - test "it unlocks an account", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/account/update_profile.json", %{ - "locked" => "false" - }) - - user = Repo.get!(User, user.id) - assert user.info.locked == false - - assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user}) - end - - # Broken before the change to class="emoji" and non-<img/> in the DB - @tag :skip - test "it formats emojos", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/account/update_profile.json", %{ - "bio" => "I love our :moominmamma:" - }) - - assert response = json_response(conn, 200) - - assert %{ - "description" => "I love our :moominmamma:", - "description_html" => - ~s{I love our <img class="emoji" alt="moominmamma" title="moominmamma" src="} <> - _ - } = response - - conn = - conn - |> get("/api/users/show.json?user_id=#{user.nickname}") - - assert response == json_response(conn, 200) - end - end - - defp valid_user(_context) do - user = insert(:user) - [user: user] - end - - defp with_credentials(conn, username, password) do - header_content = "Basic " <> Base.encode64("#{username}:#{password}") - put_req_header(conn, "authorization", header_content) - end - - describe "GET /api/search.json" do - test "it returns search results", %{conn: conn} do - user = insert(:user) - user_two = insert(:user, %{nickname: "shp@shitposter.club"}) - - {:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu"}) - {:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"}) - - conn = - conn - |> get("/api/search.json", %{"q" => "2hu", "page" => "1", "rpp" => "1"}) - - assert [status] = json_response(conn, 200) - assert status["id"] == activity.id - end - end - - describe "GET /api/statusnet/tags/timeline/:tag.json" do - test "it returns the tags timeline", %{conn: conn} do - user = insert(:user) - user_two = insert(:user, %{nickname: "shp@shitposter.club"}) - - {:ok, activity} = CommonAPI.post(user, %{"status" => "This is about #2hu"}) - {:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"}) - - conn = - conn - |> get("/api/statusnet/tags/timeline/2hu.json") - - assert [status] = json_response(conn, 200) - assert status["id"] == activity.id - end - end - - test "Convert newlines to <br> in bio", %{conn: conn} do - user = insert(:user) - - _conn = - conn - |> assign(:user, user) - |> post("/api/account/update_profile.json", %{ - "description" => "Hello,\r\nWorld! I\n am a test." - }) - - user = Repo.get!(User, user.id) - assert user.bio == "Hello,<br>World! I<br> am a test." - end - - describe "POST /api/pleroma/change_password" do - setup [:valid_user] - - test "without credentials", %{conn: conn} do - conn = post(conn, "/api/pleroma/change_password") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials and invalid password", %{conn: conn, user: current_user} do - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/change_password", %{ - "password" => "hi", - "new_password" => "newpass", - "new_password_confirmation" => "newpass" - }) - - assert json_response(conn, 200) == %{"error" => "Invalid password."} - end - - test "with credentials, valid password and new password and confirmation not matching", %{ - conn: conn, - user: current_user - } do - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/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 credentials, valid password and invalid new password", %{ - conn: conn, - user: current_user - } do - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/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 credentials, valid password and matching new password and confirmation", %{ - conn: conn, - user: current_user - } do - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/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(current_user.id) - assert Pbkdf2.checkpw("newpass", fetched_user.password_hash) == true - end - end - - describe "POST /api/pleroma/delete_account" do - setup [:valid_user] - - test "without credentials", %{conn: conn} do - conn = post(conn, "/api/pleroma/delete_account") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials and invalid password", %{conn: conn, user: current_user} do - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/delete_account", %{"password" => "hi"}) - - assert json_response(conn, 200) == %{"error" => "Invalid password."} - end - - test "with credentials and valid password", %{conn: conn, user: current_user} do - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/delete_account", %{"password" => "test"}) - - assert json_response(conn, 200) == %{"status" => "success"} - # Wait a second for the started task to end - :timer.sleep(1000) - end - end - - describe "GET /api/pleroma/friend_requests" do - test "it lists friend requests" do - user = insert(:user) - 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/pleroma/friend_requests") - - assert [relationship] = json_response(conn, 200) - assert other_user.id == relationship["id"] - end - - test "requires 'read' permission", %{conn: conn} do - token1 = insert(:oauth_token, scopes: ["write"]) - token2 = insert(:oauth_token, scopes: ["read"]) - - for token <- [token1, token2] do - conn = - conn - |> put_req_header("authorization", "Bearer #{token.token}") - |> get("/api/pleroma/friend_requests") - - if token == token1 do - assert %{"error" => "Insufficient permissions: read."} == json_response(conn, 403) - else - assert json_response(conn, 200) - end - end - end - end - - describe "POST /api/pleroma/friendships/approve" do - test "it approves a friend request" do - user = insert(:user) - 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/pleroma/friendships/approve", %{"user_id" => other_user.id}) - - assert relationship = json_response(conn, 200) - assert other_user.id == relationship["id"] - assert relationship["follows_you"] == true - end - end - - describe "POST /api/pleroma/friendships/deny" do - test "it denies a friend request" do - user = insert(:user) - 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/pleroma/friendships/deny", %{"user_id" => other_user.id}) - - assert relationship = json_response(conn, 200) - assert other_user.id == relationship["id"] - assert relationship["follows_you"] == false - end - end - - describe "GET /api/pleroma/search_user" do - test "it returns users, ordered by similarity", %{conn: conn} do - user = insert(:user, %{name: "eal"}) - user_two = insert(:user, %{name: "eal me"}) - _user_three = insert(:user, %{name: "zzz"}) - - resp = - conn - |> get(twitter_api_search__path(conn, :search_user), query: "eal me") - |> json_response(200) - - assert length(resp) == 2 - assert [user_two.id, user.id] == Enum.map(resp, fn %{"id" => id} -> id end) - end - end - - describe "POST /api/media/upload" do - setup context do - Pleroma.DataCase.ensure_local_uploader(context) - end - - test "it performs the upload and sets `data[actor]` with AP id of uploader user", %{ - conn: conn - } do - user = insert(:user) - - upload_filename = "test/fixtures/image_tmp.jpg" - File.cp!("test/fixtures/image.jpg", upload_filename) - - file = %Plug.Upload{ - content_type: "image/jpg", - path: Path.absname(upload_filename), - filename: "image.jpg" - } - - response = - conn - |> assign(:user, user) - |> put_req_header("content-type", "application/octet-stream") - |> post("/api/media/upload", %{ - "media" => file - }) - |> json_response(:ok) - - assert response["media_id"] - object = Repo.get(Object, response["media_id"]) - assert object - assert object.data["actor"] == User.ap_id(user) - end - end - - describe "POST /api/media/metadata/create" do - setup do - object = insert(:note) - user = User.get_cached_by_ap_id(object.data["actor"]) - %{object: object, user: user} - end - - test "it returns :forbidden status on attempt to modify someone else's upload", %{ - conn: conn, - object: object - } do - initial_description = object.data["name"] - another_user = insert(:user) - - conn - |> assign(:user, another_user) - |> post("/api/media/metadata/create", %{"media_id" => object.id}) - |> json_response(:forbidden) - - object = Repo.get(Object, object.id) - assert object.data["name"] == initial_description - end - - test "it updates `data[name]` of referenced Object with provided value", %{ - conn: conn, - object: object, - user: user - } do - description = "Informative description of the image. Initial value: #{object.data["name"]}}" - - conn - |> assign(:user, user) - |> post("/api/media/metadata/create", %{ - "media_id" => object.id, - "alt_text" => %{"text" => description} - }) - |> json_response(:no_content) - - object = Repo.get(Object, object.id) - assert object.data["name"] == description - end - end - - describe "POST /api/statuses/user_timeline.json?user_id=:user_id&pinned=true" do - test "it returns a list of pinned statuses", %{conn: conn} do - Pleroma.Config.put([:instance, :max_pinned_statuses], 1) - - user = insert(:user, %{name: "egor"}) - {:ok, %{id: activity_id}} = CommonAPI.post(user, %{"status" => "HI!!!"}) - {:ok, _} = CommonAPI.pin(activity_id, user) - - resp = - conn - |> get("/api/statuses/user_timeline.json", %{user_id: user.id, pinned: true}) - |> json_response(200) - - assert length(resp) == 1 - assert [%{"id" => ^activity_id, "pinned" => true}] = resp - end - end - - describe "POST /api/statuses/pin/:id" do - setup do - Pleroma.Config.put([:instance, :max_pinned_statuses], 1) - [user: insert(:user)] - end - - test "without valid credentials", %{conn: conn} do - note_activity = insert(:note_activity) - conn = post(conn, "/api/statuses/pin/#{note_activity.id}.json") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials", %{conn: conn, user: user} do - {:ok, activity} = CommonAPI.post(user, %{"status" => "test!"}) - - request_path = "/api/statuses/pin/#{activity.id}.json" - - response = - conn - |> with_credentials(user.nickname, "test") - |> post(request_path) - - user = refresh_record(user) - - assert json_response(response, 200) == - ActivityView.render("activity.json", %{user: user, for: user, activity: activity}) - end - end - - describe "POST /api/statuses/unpin/:id" do - setup do - Pleroma.Config.put([:instance, :max_pinned_statuses], 1) - [user: insert(:user)] - end - - test "without valid credentials", %{conn: conn} do - note_activity = insert(:note_activity) - conn = post(conn, "/api/statuses/unpin/#{note_activity.id}.json") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials", %{conn: conn, user: user} do - {:ok, activity} = CommonAPI.post(user, %{"status" => "test!"}) - {:ok, activity} = CommonAPI.pin(activity.id, user) - - request_path = "/api/statuses/unpin/#{activity.id}.json" - - response = - conn - |> with_credentials(user.nickname, "test") - |> post(request_path) - - user = refresh_record(user) - - assert json_response(response, 200) == - ActivityView.render("activity.json", %{user: user, for: user, activity: activity}) - 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 cbe83852e..85a9be3e0 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -1,273 +1,21 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do use Pleroma.DataCase - alias Pleroma.Activity - alias Pleroma.Object alias Pleroma.Repo + alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.UserInviteToken - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.TwitterAPI.ActivityView + alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.TwitterAPI.TwitterAPI - alias Pleroma.Web.TwitterAPI.UserView - - import Pleroma.Factory setup_all do Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) :ok end - test "create a status" do - user = insert(:user) - mentioned_user = insert(:user, %{nickname: "shp", ap_id: "shp"}) - - object_data = %{ - "type" => "Image", - "url" => [ - %{ - "type" => "Link", - "mediaType" => "image/jpg", - "href" => "http://example.org/image.jpg" - } - ], - "uuid" => 1 - } - - object = Repo.insert!(%Object{data: object_data}) - - input = %{ - "status" => - "Hello again, @shp.<script></script>\nThis is on another :firefox: line. #2hu #epic #phantasmagoric", - "media_ids" => [object.id] - } - - {:ok, activity = %Activity{}} = TwitterAPI.create_status(user, input) - object = Object.normalize(activity) - - expected_text = - "Hello again, <span class='h-card'><a data-user='#{mentioned_user.id}' class='u-url mention' href='shp'>@<span>shp</span></a></span>.<script></script><br>This is on another :firefox: line. <a class='hashtag' data-tag='2hu' href='http://localhost:4001/tag/2hu' rel='tag'>#2hu</a> <a class='hashtag' data-tag='epic' href='http://localhost:4001/tag/epic' rel='tag'>#epic</a> <a class='hashtag' data-tag='phantasmagoric' href='http://localhost:4001/tag/phantasmagoric' rel='tag'>#phantasmagoric</a><br><a href=\"http://example.org/image.jpg\" class='attachment'>image.jpg</a>" - - assert get_in(object.data, ["content"]) == expected_text - assert get_in(object.data, ["type"]) == "Note" - assert get_in(object.data, ["actor"]) == user.ap_id - assert get_in(activity.data, ["actor"]) == user.ap_id - assert Enum.member?(get_in(activity.data, ["cc"]), User.ap_followers(user)) - - assert Enum.member?( - get_in(activity.data, ["to"]), - "https://www.w3.org/ns/activitystreams#Public" - ) - - assert Enum.member?(get_in(activity.data, ["to"]), "shp") - assert activity.local == true - - assert %{"firefox" => "http://localhost:4001/emoji/Firefox.gif"} = object.data["emoji"] - - # hashtags - assert object.data["tag"] == ["2hu", "epic", "phantasmagoric"] - - # Add a context - assert is_binary(get_in(activity.data, ["context"])) - assert is_binary(get_in(object.data, ["context"])) - - assert is_list(object.data["attachment"]) - - assert activity.data["object"] == object.data["id"] - - user = User.get_cached_by_ap_id(user.ap_id) - - assert user.info.note_count == 1 - end - - test "create a status that is a reply" do - user = insert(:user) - - input = %{ - "status" => "Hello again." - } - - {:ok, activity = %Activity{}} = TwitterAPI.create_status(user, input) - object = Object.normalize(activity) - - input = %{ - "status" => "Here's your (you).", - "in_reply_to_status_id" => activity.id - } - - {:ok, reply = %Activity{}} = TwitterAPI.create_status(user, input) - reply_object = Object.normalize(reply) - - assert get_in(reply.data, ["context"]) == get_in(activity.data, ["context"]) - - assert get_in(reply_object.data, ["context"]) == get_in(object.data, ["context"]) - - assert get_in(reply_object.data, ["inReplyTo"]) == get_in(activity.data, ["object"]) - assert Activity.get_in_reply_to_activity(reply).id == activity.id - end - - test "Follow another user using user_id" do - user = insert(:user) - followed = insert(:user) - - {:ok, user, followed, _activity} = TwitterAPI.follow(user, %{"user_id" => followed.id}) - assert User.ap_followers(followed) in user.following - - {:ok, _, _, _} = TwitterAPI.follow(user, %{"user_id" => followed.id}) - end - - test "Follow another user using screen_name" do - user = insert(:user) - followed = insert(:user) - - {:ok, user, followed, _activity} = - TwitterAPI.follow(user, %{"screen_name" => followed.nickname}) - - assert User.ap_followers(followed) in user.following - - followed = User.get_cached_by_ap_id(followed.ap_id) - assert followed.info.follower_count == 1 - - {:ok, _, _, _} = TwitterAPI.follow(user, %{"screen_name" => followed.nickname}) - end - - test "Unfollow another user using user_id" do - unfollowed = insert(:user) - user = insert(:user, %{following: [User.ap_followers(unfollowed)]}) - ActivityPub.follow(user, unfollowed) - - {:ok, user, unfollowed} = TwitterAPI.unfollow(user, %{"user_id" => unfollowed.id}) - assert user.following == [] - - {:error, msg} = TwitterAPI.unfollow(user, %{"user_id" => unfollowed.id}) - assert msg == "Not subscribed!" - end - - test "Unfollow another user using screen_name" do - unfollowed = insert(:user) - user = insert(:user, %{following: [User.ap_followers(unfollowed)]}) - - ActivityPub.follow(user, unfollowed) - - {:ok, user, unfollowed} = TwitterAPI.unfollow(user, %{"screen_name" => unfollowed.nickname}) - assert user.following == [] - - {:error, msg} = TwitterAPI.unfollow(user, %{"screen_name" => unfollowed.nickname}) - assert msg == "Not subscribed!" - end - - test "Block another user using user_id" do - user = insert(:user) - blocked = insert(:user) - - {:ok, user, blocked} = TwitterAPI.block(user, %{"user_id" => blocked.id}) - assert User.blocks?(user, blocked) - end - - test "Block another user using screen_name" do - user = insert(:user) - blocked = insert(:user) - - {:ok, user, blocked} = TwitterAPI.block(user, %{"screen_name" => blocked.nickname}) - assert User.blocks?(user, blocked) - end - - test "Unblock another user using user_id" do - unblocked = insert(:user) - user = insert(:user) - {:ok, user, _unblocked} = TwitterAPI.block(user, %{"user_id" => unblocked.id}) - - {:ok, user, _unblocked} = TwitterAPI.unblock(user, %{"user_id" => unblocked.id}) - assert user.info.blocks == [] - end - - test "Unblock another user using screen_name" do - unblocked = insert(:user) - user = insert(:user) - {:ok, user, _unblocked} = TwitterAPI.block(user, %{"screen_name" => unblocked.nickname}) - - {:ok, user, _unblocked} = TwitterAPI.unblock(user, %{"screen_name" => unblocked.nickname}) - assert user.info.blocks == [] - end - - test "upload a file" do - user = insert(:user) - - file = %Plug.Upload{ - content_type: "image/jpg", - path: Path.absname("test/fixtures/image.jpg"), - filename: "an_image.jpg" - } - - response = TwitterAPI.upload(file, user) - - assert is_binary(response) - end - - test "it favorites a status, returns the updated activity" do - user = insert(:user) - other_user = insert(:user) - note_activity = insert(:note_activity) - - {:ok, status} = TwitterAPI.fav(user, note_activity.id) - updated_activity = Activity.get_by_ap_id(note_activity.data["id"]) - assert ActivityView.render("activity.json", %{activity: updated_activity})["fave_num"] == 1 - - object = Object.normalize(note_activity) - - assert object.data["like_count"] == 1 - - assert status == updated_activity - - {:ok, _status} = TwitterAPI.fav(other_user, note_activity.id) - - object = Object.normalize(note_activity) - - assert object.data["like_count"] == 2 - - updated_activity = Activity.get_by_ap_id(note_activity.data["id"]) - assert ActivityView.render("activity.json", %{activity: updated_activity})["fave_num"] == 2 - end - - test "it unfavorites a status, returns the updated activity" do - user = insert(:user) - note_activity = insert(:note_activity) - object = Object.normalize(note_activity) - - {:ok, _like_activity, _object} = ActivityPub.like(user, object) - updated_activity = Activity.get_by_ap_id(note_activity.data["id"]) - - assert ActivityView.render("activity.json", activity: updated_activity)["fave_num"] == 1 - - {:ok, activity} = TwitterAPI.unfav(user, note_activity.id) - - assert ActivityView.render("activity.json", activity: activity)["fave_num"] == 0 - end - - test "it retweets a status and returns the retweet" do - user = insert(:user) - note_activity = insert(:note_activity) - - {:ok, status} = TwitterAPI.repeat(user, note_activity.id) - updated_activity = Activity.get_by_ap_id(note_activity.data["id"]) - - assert status == updated_activity - end - - test "it unretweets an already retweeted status" do - user = insert(:user) - note_activity = insert(:note_activity) - - {:ok, _status} = TwitterAPI.repeat(user, note_activity.id) - {:ok, status} = TwitterAPI.unrepeat(user, note_activity.id) - updated_activity = Activity.get_by_ap_id(note_activity.data["id"]) - - assert status == updated_activity - end - test "it registers a new user and returns the user." do data = %{ "nickname" => "lain", @@ -281,8 +29,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do fetched_user = User.get_cached_by_nickname("lain") - assert UserView.render("show.json", %{user: user}) == - UserView.render("show.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 @@ -299,8 +47,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do fetched_user = User.get_cached_by_nickname("lain") - assert UserView.render("show.json", %{user: user}) == - UserView.render("show.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 @@ -321,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) @@ -360,7 +109,9 @@ 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 @@ -397,8 +148,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do assert invite.used == true - assert UserView.render("show.json", %{user: user}) == - UserView.render("show.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 @@ -462,8 +213,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do {:ok, user} = TwitterAPI.register_user(data) fetched_user = User.get_cached_by_nickname("vinny") - assert UserView.render("show.json", %{user: user}) == - UserView.render("show.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} @@ -537,8 +288,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do assert invite.used == true - assert UserView.render("show.json", %{user: user}) == - UserView.render("show.json", %{user: fetched_user}) + assert AccountView.render("show.json", %{user: user}) == + AccountView.render("show.json", %{user: fetched_user}) data = %{ "nickname" => "GrimReaper", @@ -588,8 +339,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do refute invite.used - assert UserView.render("show.json", %{user: user}) == - UserView.render("show.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 @@ -612,8 +363,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do invite = Repo.get_by(UserInviteToken, token: invite.token) assert invite.used == true - assert UserView.render("show.json", %{user: user}) == - UserView.render("show.json", %{user: fetched_user}) + assert AccountView.render("show.json", %{user: user}) == + AccountView.render("show.json", %{user: fetched_user}) data = %{ "nickname" => "GrimReaper", @@ -689,31 +440,9 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do refute User.get_cached_by_nickname("lain") end - test "it assigns an integer conversation_id" do - note_activity = insert(:note_activity) - status = ActivityView.render("activity.json", activity: note_activity) - - assert is_number(status["statusnet_conversation_id"]) - end - setup do Supervisor.terminate_child(Pleroma.Supervisor, Cachex) Supervisor.restart_child(Pleroma.Supervisor, Cachex) :ok end - - describe "fetching a user by uri" do - test "fetches a user by uri" do - id = "https://mastodon.social/users/lambadalambda" - user = insert(:user) - {:ok, represented} = TwitterAPI.get_external_profile(user, id) - remote = User.get_cached_by_ap_id(id) - - assert represented["id"] == UserView.render("show.json", %{user: remote, for: user})["id"] - - # Also fetches the feed. - # assert Activity.get_create_by_object_ap_id("tag:mastodon.social,2017-04-05:objectId=1641750:objectType=Status") - # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength - end - end end diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs index 3d699e1df..5d60c0d51 100644 --- a/test/web/twitter_api/util_controller_test.exs +++ b/test/web/twitter_api/util_controller_test.exs @@ -4,11 +4,11 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do use Pleroma.Web.ConnCase + use Oban.Testing, repo: Pleroma.Repo - alias Pleroma.Notification - alias Pleroma.Repo + alias Pleroma.Tests.ObanHelpers alias Pleroma.User - alias Pleroma.Web.CommonAPI + import Pleroma.Factory import Mock @@ -17,27 +17,56 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do :ok end + 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", %{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} + ]) do + response = + conn + |> post("/api/pleroma/follow_import", %{"list" => %Plug.Upload{path: "follow_list.txt"}}) + |> json_response(:ok) + + 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" }) @@ -46,19 +75,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 @@ -67,65 +98,143 @@ 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 - end - describe "POST /api/pleroma/notifications/read" do - test "it marks a single notification as read", %{conn: conn} do - user1 = insert(:user) + test "it imports blocks users from file", %{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) - - conn - |> assign(:user, user1) - |> post("/api/pleroma/notifications/read", %{"id" => "#{notification1.id}"}) - |> json_response(:ok) + user3 = insert(:user) - assert Repo.get(Notification, notification1.id).seen - refute Repo.get(Notification, notification2.id).seen + with_mocks([ + {File, [], read!: fn "blocks_list.txt" -> "#{user2.ap_id} #{user3.ap_id}" end} + ]) do + response = + conn + |> post("/api/pleroma/blocks_import", %{"list" => %Plug.Upload{path: "blocks_list.txt"}}) + |> json_response(:ok) + + 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 %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 %{ - "followers" => false, - "follows" => true, - "non_follows" => true, - "non_followers" => true - } == user.info.notification_settings + assert %Pleroma.User.NotificationSetting{ + followers: true, + follows: true, + non_follows: true, + non_followers: true, + privacy_option: true + } == user.notification_settings end end - describe "GET /api/statusnet/config.json" do + describe "GET /api/statusnet/config" do + test "it returns config in xml format", %{conn: conn} do + instance = Pleroma.Config.get(:instance) + + response = + conn + |> put_req_header("accept", "application/xml") + |> get("/api/statusnet/config") + |> response(:ok) + + assert response == + "<config>\n<site>\n<name>#{Keyword.get(instance, :name)}</name>\n<site>#{ + Pleroma.Web.base_url() + }</site>\n<textlimit>#{Keyword.get(instance, :limit)}</textlimit>\n<closed>#{ + !Keyword.get(instance, :registrations_open) + }</closed>\n</site>\n</config>\n" + end + + test "it returns config in json format", %{conn: conn} do + instance = Pleroma.Config.get(:instance) + Pleroma.Config.put([:instance, :managed_config], true) + Pleroma.Config.put([:instance, :registrations_open], false) + Pleroma.Config.put([:instance, :invites_enabled], true) + Pleroma.Config.put([:instance, :public], false) + Pleroma.Config.put([:frontend_configurations, :pleroma_fe], %{theme: "asuka-hospital"}) + + response = + conn + |> put_req_header("accept", "application/json") + |> get("/api/statusnet/config") + |> json_response(:ok) + + expected_data = %{ + "site" => %{ + "accountActivationRequired" => "0", + "closed" => "1", + "description" => Keyword.get(instance, :description), + "invitesEnabled" => "1", + "name" => Keyword.get(instance, :name), + "pleromafe" => %{"theme" => "asuka-hospital"}, + "private" => "1", + "safeDMMentionsEnabled" => "0", + "server" => Pleroma.Web.base_url(), + "textlimit" => to_string(Keyword.get(instance, :limit)), + "uploadlimit" => %{ + "avatarlimit" => to_string(Keyword.get(instance, :avatar_upload_limit)), + "backgroundlimit" => to_string(Keyword.get(instance, :background_upload_limit)), + "bannerlimit" => to_string(Keyword.get(instance, :banner_upload_limit)), + "uploadlimit" => to_string(Keyword.get(instance, :upload_limit)) + }, + "vapidPublicKey" => Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) + } + } + + assert response == expected_data + end + test "returns the state of safe_dm_mentions flag", %{conn: conn} do - option = Pleroma.Config.get([:instance, :safe_dm_mentions]) Pleroma.Config.put([:instance, :safe_dm_mentions], true) response = @@ -143,8 +252,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do |> json_response(:ok) assert response["site"]["safeDMMentionsEnabled"] == "0" - - Pleroma.Config.put([:instance, :safe_dm_mentions], option) end test "it returns the managed config", %{conn: conn} do @@ -210,38 +317,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do end end - describe "GET /ostatus_subscribe?acct=...." 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 - end - describe "GET /api/pleroma/healthcheck" do - setup do - config_healthcheck = Pleroma.Config.get([:instance, :healthcheck]) - - on_exit(fn -> - Pleroma.Config.put([:instance, :healthcheck], config_healthcheck) - end) - - :ok - end + clear_config([:instance, :healthcheck]) test "returns 503 when healthcheck disabled", %{conn: conn} do Pleroma.Config.put([:instance, :healthcheck], false) @@ -274,7 +351,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, @@ -296,20 +373,301 @@ 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.deactivated == true + end + + test "with valid permissions and invalid password, it returns an error", %{conn: conn} do + user = insert(:user) + + response = + conn + |> post("/api/pleroma/disable_account", %{"password" => "test1"}) + |> json_response(:ok) + assert response == %{"error" => "Invalid password."} user = User.get_cached_by_id(user.id) - assert user.info.deactivated == true + refute user.deactivated + end + end + + describe "GET /api/statusnet/version" do + test "it returns version in xml format", %{conn: conn} do + response = + conn + |> put_req_header("accept", "application/xml") + |> get("/api/statusnet/version") + |> response(:ok) + + assert response == "<version>#{Pleroma.Application.named_version()}</version>" + end + + test "it returns version in json format", %{conn: conn} do + response = + conn + |> put_req_header("accept", "application/json") + |> get("/api/statusnet/version") + |> json_response(:ok) + + assert response == "#{Pleroma.Application.named_version()}" + end + end + + describe "POST /main/ostatus - remote_subscribe/2" do + test "renders subscribe form", %{conn: conn} do + user = insert(:user) + + response = + conn + |> post("/main/ostatus", %{"nickname" => user.nickname, "profile" => ""}) + |> response(:ok) + + refute response =~ "Could not find user" + assert response =~ "Remotely follow #{user.nickname}" + end + + test "renders subscribe form with error when user not found", %{conn: conn} do + response = + conn + |> post("/main/ostatus", %{"nickname" => "nickname", "profile" => ""}) + |> response(:ok) + + assert response =~ "Could not find user" + refute response =~ "Remotely follow" + end + + test "it redirect to webfinger url", %{conn: conn} do + user = insert(:user) + user2 = insert(:user, ap_id: "shp@social.heldscal.la") + + conn = + conn + |> post("/main/ostatus", %{ + "user" => %{"nickname" => user.nickname, "profile" => user2.ap_id} + }) + + assert redirected_to(conn) == + "https://social.heldscal.la/main/ostatussub?profile=#{user.ap_id}" + end + + test "it renders form with error when user not found", %{conn: conn} do + user2 = insert(:user, ap_id: "shp@social.heldscal.la") + + response = + conn + |> post("/main/ostatus", %{"user" => %{"nickname" => "jimm", "profile" => user2.ap_id}}) + |> response(:ok) + + assert response =~ "Something went wrong." + end + end + + test "it returns new captcha", %{conn: conn} do + with_mock Pleroma.Captcha, + new: fn -> "test_captcha" end do + resp = + conn + |> get("/api/pleroma/captcha") + |> response(200) + + assert resp == "\"test_captcha\"" + assert called(Pleroma.Captcha.new()) + end + end + + describe "POST /api/pleroma/change_email" do + setup do: oauth_access(["write:accounts"]) + + test "without permissions", %{conn: conn} do + conn = + conn + |> assign(:token, nil) + |> post("/api/pleroma/change_email") + + 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_email", %{ + "password" => "hi", + "email" => "test@test.com" + }) + + assert json_response(conn, 200) == %{"error" => "Invalid password."} + end + + test "with proper permissions, valid password and invalid email", %{ + conn: conn + } do + conn = + post(conn, "/api/pleroma/change_email", %{ + "password" => "test", + "email" => "foobar" + }) + + assert json_response(conn, 200) == %{"error" => "Email has invalid format."} + end + + test "with proper permissions, valid password and no email", %{ + conn: conn + } do + conn = + post(conn, "/api/pleroma/change_email", %{ + "password" => "test" + }) + + assert json_response(conn, 200) == %{"error" => "Email can't be blank."} + end + + test "with proper permissions, valid password and blank email", %{ + conn: conn + } do + conn = + post(conn, "/api/pleroma/change_email", %{ + "password" => "test", + "email" => "" + }) + + assert json_response(conn, 200) == %{"error" => "Email can't be blank."} + end + + test "with proper permissions, valid password and non unique email", %{ + conn: conn + } do + user = insert(:user) + + conn = + post(conn, "/api/pleroma/change_email", %{ + "password" => "test", + "email" => user.email + }) + + assert json_response(conn, 200) == %{"error" => "Email has already been taken."} + end + + test "with proper permissions, valid password and valid email", %{ + conn: conn + } do + conn = + post(conn, "/api/pleroma/change_email", %{ + "password" => "test", + "email" => "cofe@foobar.com" + }) + + 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/twitter_api/views/activity_view_test.exs b/test/web/twitter_api/views/activity_view_test.exs deleted file mode 100644 index 56d861efb..000000000 --- a/test/web/twitter_api/views/activity_view_test.exs +++ /dev/null @@ -1,384 +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.TwitterAPI.ActivityViewTest do - use Pleroma.DataCase - - alias Pleroma.Activity - alias Pleroma.Object - alias Pleroma.Repo - alias Pleroma.User - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.CommonAPI - alias Pleroma.Web.CommonAPI.Utils - alias Pleroma.Web.TwitterAPI.ActivityView - alias Pleroma.Web.TwitterAPI.UserView - - import Pleroma.Factory - import Tesla.Mock - - setup do - mock(fn env -> apply(HttpRequestMock, :request, [env]) end) - :ok - end - - import Mock - - test "returns a temporary ap_id based user for activities missing db users" do - user = insert(:user) - - {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"}) - - Repo.delete(user) - Cachex.clear(:user_cache) - - %{"user" => tw_user} = ActivityView.render("activity.json", activity: activity) - - assert tw_user["screen_name"] == "erroruser@example.com" - assert tw_user["name"] == user.ap_id - assert tw_user["statusnet_profile_url"] == user.ap_id - end - - test "tries to get a user by nickname if fetching by ap_id doesn't work" do - user = insert(:user) - - {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"}) - - {:ok, user} = - user - |> Ecto.Changeset.change(%{ap_id: "#{user.ap_id}/extension/#{user.nickname}"}) - |> Repo.update() - - Cachex.clear(:user_cache) - - result = ActivityView.render("activity.json", activity: activity) - assert result["user"]["id"] == user.id - end - - test "tells if the message is muted for some reason" do - user = insert(:user) - other_user = insert(:user) - - {:ok, user} = User.mute(user, other_user) - - {:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"}) - status = ActivityView.render("activity.json", %{activity: activity}) - - assert status["muted"] == false - - status = ActivityView.render("activity.json", %{activity: activity, for: user}) - - assert status["muted"] == true - end - - test "a create activity with a html status" do - text = """ - #Bike log - Commute Tuesday\nhttps://pla.bike/posts/20181211/\n#cycling #CHScycling #commute\nMVIMG_20181211_054020.jpg - """ - - {:ok, activity} = CommonAPI.post(insert(:user), %{"status" => text}) - - result = ActivityView.render("activity.json", activity: activity) - - assert result["statusnet_html"] == - "<a class=\"hashtag\" data-tag=\"bike\" href=\"http://localhost:4001/tag/bike\" rel=\"tag\">#Bike</a> log - Commute Tuesday<br /><a href=\"https://pla.bike/posts/20181211/\">https://pla.bike/posts/20181211/</a><br /><a class=\"hashtag\" data-tag=\"cycling\" href=\"http://localhost:4001/tag/cycling\" rel=\"tag\">#cycling</a> <a class=\"hashtag\" data-tag=\"chscycling\" href=\"http://localhost:4001/tag/chscycling\" rel=\"tag\">#CHScycling</a> <a class=\"hashtag\" data-tag=\"commute\" href=\"http://localhost:4001/tag/commute\" rel=\"tag\">#commute</a><br />MVIMG_20181211_054020.jpg" - - assert result["text"] == - "#Bike log - Commute Tuesday\nhttps://pla.bike/posts/20181211/\n#cycling #CHScycling #commute\nMVIMG_20181211_054020.jpg" - end - - test "a create activity with a summary containing emoji" do - {:ok, activity} = - CommonAPI.post(insert(:user), %{ - "spoiler_text" => ":firefox: meow", - "status" => "." - }) - - result = ActivityView.render("activity.json", activity: activity) - - expected = ":firefox: meow" - - expected_html = - "<img class=\"emoji\" alt=\"firefox\" title=\"firefox\" src=\"http://localhost:4001/emoji/Firefox.gif\" /> meow" - - assert result["summary"] == expected - assert result["summary_html"] == expected_html - end - - test "a create activity with a summary containing invalid HTML" do - {:ok, activity} = - CommonAPI.post(insert(:user), %{ - "spoiler_text" => "<span style=\"color: magenta; font-size: 32px;\">meow</span>", - "status" => "." - }) - - result = ActivityView.render("activity.json", activity: activity) - - expected = "meow" - - assert result["summary"] == expected - assert result["summary_html"] == expected - end - - test "a create activity with a note" do - user = insert(:user) - other_user = insert(:user, %{nickname: "shp"}) - - {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"}) - object = Object.normalize(activity) - - result = ActivityView.render("activity.json", activity: activity) - - convo_id = Utils.context_to_conversation_id(object.data["context"]) - - expected = %{ - "activity_type" => "post", - "attachments" => [], - "attentions" => [ - UserView.render("show.json", %{user: other_user}) - ], - "created_at" => object.data["published"] |> Utils.date_to_asctime(), - "external_url" => object.data["id"], - "fave_num" => 0, - "favorited" => false, - "id" => activity.id, - "in_reply_to_status_id" => nil, - "in_reply_to_screen_name" => nil, - "in_reply_to_user_id" => nil, - "in_reply_to_profileurl" => nil, - "in_reply_to_ostatus_uri" => nil, - "is_local" => true, - "is_post_verb" => true, - "possibly_sensitive" => false, - "repeat_num" => 0, - "repeated" => false, - "pinned" => false, - "statusnet_conversation_id" => convo_id, - "summary" => "", - "summary_html" => "", - "statusnet_html" => - "Hey <span class=\"h-card\"><a data-user=\"#{other_user.id}\" class=\"u-url mention\" href=\"#{ - other_user.ap_id - }\">@<span>shp</span></a></span>!", - "tags" => [], - "text" => "Hey @shp!", - "uri" => object.data["id"], - "user" => UserView.render("show.json", %{user: user}), - "visibility" => "direct", - "card" => nil, - "muted" => false - } - - assert result == expected - end - - test "a list of activities" do - user = insert(:user) - other_user = insert(:user, %{nickname: "shp"}) - {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"}) - object = Object.normalize(activity) - - convo_id = Utils.context_to_conversation_id(object.data["context"]) - - mocks = [ - { - Utils, - [:passthrough], - [context_to_conversation_id: fn _ -> false end] - }, - { - User, - [:passthrough], - [get_cached_by_ap_id: fn _ -> nil end] - } - ] - - with_mocks mocks do - [result] = ActivityView.render("index.json", activities: [activity]) - - assert result["statusnet_conversation_id"] == convo_id - assert result["user"] - refute called(Utils.context_to_conversation_id(:_)) - refute called(User.get_cached_by_ap_id(user.ap_id)) - refute called(User.get_cached_by_ap_id(other_user.ap_id)) - end - end - - test "an activity that is a reply" do - user = insert(:user) - other_user = insert(:user, %{nickname: "shp"}) - - {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"}) - - {:ok, answer} = - CommonAPI.post(other_user, %{"status" => "Hi!", "in_reply_to_status_id" => activity.id}) - - result = ActivityView.render("activity.json", %{activity: answer}) - - assert result["in_reply_to_status_id"] == activity.id - end - - test "a like activity" do - user = insert(:user) - other_user = insert(:user, %{nickname: "shp"}) - - {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"}) - {:ok, like, _object} = CommonAPI.favorite(activity.id, other_user) - - result = ActivityView.render("activity.json", activity: like) - activity = Pleroma.Activity.get_by_ap_id(activity.data["id"]) - - expected = %{ - "activity_type" => "like", - "created_at" => like.data["published"] |> Utils.date_to_asctime(), - "external_url" => like.data["id"], - "id" => like.id, - "in_reply_to_status_id" => activity.id, - "is_local" => true, - "is_post_verb" => false, - "favorited_status" => ActivityView.render("activity.json", activity: activity), - "statusnet_html" => "shp favorited a status.", - "text" => "shp favorited a status.", - "uri" => "tag:#{like.data["id"]}:objectType=Favourite", - "user" => UserView.render("show.json", user: other_user) - } - - assert result == expected - end - - test "a like activity for deleted post" do - user = insert(:user) - other_user = insert(:user, %{nickname: "shp"}) - - {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"}) - {:ok, like, _object} = CommonAPI.favorite(activity.id, other_user) - CommonAPI.delete(activity.id, user) - - result = ActivityView.render("activity.json", activity: like) - - expected = %{ - "activity_type" => "like", - "created_at" => like.data["published"] |> Utils.date_to_asctime(), - "external_url" => like.data["id"], - "id" => like.id, - "in_reply_to_status_id" => nil, - "is_local" => true, - "is_post_verb" => false, - "favorited_status" => nil, - "statusnet_html" => "shp favorited a status.", - "text" => "shp favorited a status.", - "uri" => "tag:#{like.data["id"]}:objectType=Favourite", - "user" => UserView.render("show.json", user: other_user) - } - - assert result == expected - end - - test "an announce activity" do - user = insert(:user) - other_user = insert(:user, %{nickname: "shp"}) - - {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"}) - {:ok, announce, object} = CommonAPI.repeat(activity.id, other_user) - - convo_id = Utils.context_to_conversation_id(object.data["context"]) - - activity = Activity.get_by_id(activity.id) - - result = ActivityView.render("activity.json", activity: announce) - - expected = %{ - "activity_type" => "repeat", - "created_at" => announce.data["published"] |> Utils.date_to_asctime(), - "external_url" => announce.data["id"], - "id" => announce.id, - "is_local" => true, - "is_post_verb" => false, - "statusnet_html" => "shp repeated a status.", - "text" => "shp repeated a status.", - "uri" => "tag:#{announce.data["id"]}:objectType=note", - "user" => UserView.render("show.json", user: other_user), - "retweeted_status" => ActivityView.render("activity.json", activity: activity), - "statusnet_conversation_id" => convo_id - } - - assert result == expected - end - - test "A follow activity" do - user = insert(:user) - other_user = insert(:user, %{nickname: "shp"}) - - {:ok, follower} = User.follow(user, other_user) - {:ok, follow} = ActivityPub.follow(follower, other_user) - - result = ActivityView.render("activity.json", activity: follow) - - expected = %{ - "activity_type" => "follow", - "attentions" => [], - "created_at" => follow.data["published"] |> Utils.date_to_asctime(), - "external_url" => follow.data["id"], - "id" => follow.id, - "in_reply_to_status_id" => nil, - "is_local" => true, - "is_post_verb" => false, - "statusnet_html" => "#{user.nickname} started following shp", - "text" => "#{user.nickname} started following shp", - "user" => UserView.render("show.json", user: user) - } - - assert result == expected - end - - test "a delete activity" do - user = insert(:user) - - {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"}) - {:ok, delete} = CommonAPI.delete(activity.id, user) - - result = ActivityView.render("activity.json", activity: delete) - - expected = %{ - "activity_type" => "delete", - "attentions" => [], - "created_at" => delete.data["published"] |> Utils.date_to_asctime(), - "external_url" => delete.data["id"], - "id" => delete.id, - "in_reply_to_status_id" => nil, - "is_local" => true, - "is_post_verb" => false, - "statusnet_html" => "deleted notice {{tag", - "text" => "deleted notice {{tag", - "uri" => Object.normalize(delete).data["id"], - "user" => UserView.render("show.json", user: user) - } - - assert result == expected - end - - test "a peertube video" do - {:ok, object} = - Pleroma.Object.Fetcher.fetch_object_from_id( - "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3" - ) - - %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"]) - - result = ActivityView.render("activity.json", activity: activity) - - assert length(result["attachments"]) == 1 - assert result["summary"] == "Friday Night" - end - - test "special characters are not escaped in text field for status created" do - text = "<3 is on the way" - - {:ok, activity} = CommonAPI.post(insert(:user), %{"status" => text}) - - result = ActivityView.render("activity.json", activity: activity) - - assert result["text"] == text - end -end diff --git a/test/web/twitter_api/views/notification_view_test.exs b/test/web/twitter_api/views/notification_view_test.exs deleted file mode 100644 index 6baeeaf63..000000000 --- a/test/web/twitter_api/views/notification_view_test.exs +++ /dev/null @@ -1,112 +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.TwitterAPI.NotificationViewTest do - use Pleroma.DataCase - - alias Pleroma.Notification - alias Pleroma.User - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.CommonAPI.Utils - alias Pleroma.Web.TwitterAPI.ActivityView - alias Pleroma.Web.TwitterAPI.NotificationView - alias Pleroma.Web.TwitterAPI.TwitterAPI - alias Pleroma.Web.TwitterAPI.UserView - - import Pleroma.Factory - - setup do - user = insert(:user, bio: "<span>Here's some html</span>") - [user: user] - end - - test "A follow notification" do - note_activity = insert(:note_activity) - user = User.get_cached_by_ap_id(note_activity.data["actor"]) - follower = insert(:user) - - {:ok, follower} = User.follow(follower, user) - {:ok, activity} = ActivityPub.follow(follower, user) - Cachex.put(:user_cache, "user_info:#{user.id}", User.user_info(Repo.get!(User, user.id))) - [follow_notif] = Notification.for_user(user) - - represented = %{ - "created_at" => follow_notif.inserted_at |> Utils.format_naive_asctime(), - "from_profile" => UserView.render("show.json", %{user: follower, for: user}), - "id" => follow_notif.id, - "is_seen" => 0, - "notice" => ActivityView.render("activity.json", %{activity: activity, for: user}), - "ntype" => "follow" - } - - assert represented == - NotificationView.render("notification.json", %{notification: follow_notif, for: user}) - end - - test "A mention notification" do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity} = - TwitterAPI.create_status(other_user, %{"status" => "Päivää, @#{user.nickname}"}) - - [notification] = Notification.for_user(user) - - represented = %{ - "created_at" => notification.inserted_at |> Utils.format_naive_asctime(), - "from_profile" => UserView.render("show.json", %{user: other_user, for: user}), - "id" => notification.id, - "is_seen" => 0, - "notice" => ActivityView.render("activity.json", %{activity: activity, for: user}), - "ntype" => "mention" - } - - assert represented == - NotificationView.render("notification.json", %{notification: notification, for: user}) - end - - test "A retweet notification" do - note_activity = insert(:note_activity) - user = User.get_cached_by_ap_id(note_activity.data["actor"]) - repeater = insert(:user) - - {:ok, _activity} = TwitterAPI.repeat(repeater, note_activity.id) - [notification] = Notification.for_user(user) - - represented = %{ - "created_at" => notification.inserted_at |> Utils.format_naive_asctime(), - "from_profile" => UserView.render("show.json", %{user: repeater, for: user}), - "id" => notification.id, - "is_seen" => 0, - "notice" => - ActivityView.render("activity.json", %{activity: notification.activity, for: user}), - "ntype" => "repeat" - } - - assert represented == - NotificationView.render("notification.json", %{notification: notification, for: user}) - end - - test "A like notification" do - note_activity = insert(:note_activity) - user = User.get_cached_by_ap_id(note_activity.data["actor"]) - liker = insert(:user) - - {:ok, _activity} = TwitterAPI.fav(liker, note_activity.id) - [notification] = Notification.for_user(user) - - represented = %{ - "created_at" => notification.inserted_at |> Utils.format_naive_asctime(), - "from_profile" => UserView.render("show.json", %{user: liker, for: user}), - "id" => notification.id, - "is_seen" => 0, - "notice" => - ActivityView.render("activity.json", %{activity: notification.activity, for: user}), - "ntype" => "like" - } - - assert represented == - NotificationView.render("notification.json", %{notification: notification, for: user}) - end -end diff --git a/test/web/twitter_api/views/user_view_test.exs b/test/web/twitter_api/views/user_view_test.exs deleted file mode 100644 index 70c5a0b7f..000000000 --- a/test/web/twitter_api/views/user_view_test.exs +++ /dev/null @@ -1,323 +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.TwitterAPI.UserViewTest do - use Pleroma.DataCase - - alias Pleroma.User - alias Pleroma.Web.CommonAPI.Utils - alias Pleroma.Web.TwitterAPI.UserView - - import Pleroma.Factory - - setup do - user = insert(:user, bio: "<span>Here's some html</span>") - [user: user] - end - - test "A user with only a nickname", %{user: user} do - user = %{user | name: nil, nickname: "scarlett@catgirl.science"} - represented = UserView.render("show.json", %{user: user}) - assert represented["name"] == user.nickname - assert represented["name_html"] == user.nickname - end - - test "A user with an avatar object", %{user: user} do - image = "image" - user = %{user | avatar: %{"url" => [%{"href" => image}]}} - represented = UserView.render("show.json", %{user: user}) - assert represented["profile_image_url"] == image - end - - test "A user with emoji in username" do - expected = - "<img class=\"emoji\" alt=\"karjalanpiirakka\" title=\"karjalanpiirakka\" src=\"/file.png\" /> man" - - user = - insert(:user, %{ - info: %{ - source_data: %{ - "tag" => [ - %{ - "type" => "Emoji", - "icon" => %{"url" => "/file.png"}, - "name" => ":karjalanpiirakka:" - } - ] - } - }, - name: ":karjalanpiirakka: man" - }) - - represented = UserView.render("show.json", %{user: user}) - assert represented["name_html"] == expected - end - - test "A user" do - note_activity = insert(:note_activity) - user = User.get_cached_by_ap_id(note_activity.data["actor"]) - {:ok, user} = User.update_note_count(user) - follower = insert(:user) - second_follower = insert(:user) - - User.follow(follower, user) - User.follow(second_follower, user) - User.follow(user, follower) - {:ok, user} = User.update_follower_count(user) - Cachex.put(:user_cache, "user_info:#{user.id}", User.user_info(Repo.get!(User, user.id))) - - image = "http://localhost:4001/images/avi.png" - banner = "http://localhost:4001/images/banner.png" - - represented = %{ - "id" => user.id, - "name" => user.name, - "screen_name" => user.nickname, - "name_html" => user.name, - "description" => HtmlSanitizeEx.strip_tags(user.bio |> String.replace("<br>", "\n")), - "description_html" => HtmlSanitizeEx.basic_html(user.bio), - "created_at" => user.inserted_at |> Utils.format_naive_asctime(), - "favourites_count" => 0, - "statuses_count" => 1, - "friends_count" => 1, - "followers_count" => 2, - "profile_image_url" => image, - "profile_image_url_https" => image, - "profile_image_url_profile_size" => image, - "profile_image_url_original" => image, - "following" => false, - "follows_you" => false, - "statusnet_blocking" => false, - "statusnet_profile_url" => user.ap_id, - "cover_photo" => banner, - "background_image" => nil, - "is_local" => true, - "locked" => false, - "hide_follows" => false, - "hide_followers" => false, - "fields" => [], - "pleroma" => %{ - "confirmation_pending" => false, - "tags" => [], - "skip_thread_containment" => false - }, - "rights" => %{"admin" => false, "delete_others_notice" => false}, - "role" => "member" - } - - assert represented == UserView.render("show.json", %{user: user}) - end - - test "User exposes settings for themselves and only for themselves", %{user: user} do - as_user = UserView.render("show.json", %{user: user, for: user}) - assert as_user["default_scope"] == user.info.default_scope - assert as_user["no_rich_text"] == user.info.no_rich_text - assert as_user["pleroma"]["notification_settings"] == user.info.notification_settings - as_stranger = UserView.render("show.json", %{user: user}) - refute as_stranger["default_scope"] - refute as_stranger["no_rich_text"] - refute as_stranger["pleroma"]["notification_settings"] - end - - test "A user for a given other follower", %{user: user} do - follower = insert(:user, %{following: [User.ap_followers(user)]}) - {:ok, user} = User.update_follower_count(user) - image = "http://localhost:4001/images/avi.png" - banner = "http://localhost:4001/images/banner.png" - - represented = %{ - "id" => user.id, - "name" => user.name, - "screen_name" => user.nickname, - "name_html" => user.name, - "description" => HtmlSanitizeEx.strip_tags(user.bio |> String.replace("<br>", "\n")), - "description_html" => HtmlSanitizeEx.basic_html(user.bio), - "created_at" => user.inserted_at |> Utils.format_naive_asctime(), - "favourites_count" => 0, - "statuses_count" => 0, - "friends_count" => 0, - "followers_count" => 1, - "profile_image_url" => image, - "profile_image_url_https" => image, - "profile_image_url_profile_size" => image, - "profile_image_url_original" => image, - "following" => true, - "follows_you" => false, - "statusnet_blocking" => false, - "statusnet_profile_url" => user.ap_id, - "cover_photo" => banner, - "background_image" => nil, - "is_local" => true, - "locked" => false, - "hide_follows" => false, - "hide_followers" => false, - "fields" => [], - "pleroma" => %{ - "confirmation_pending" => false, - "tags" => [], - "skip_thread_containment" => false - }, - "rights" => %{"admin" => false, "delete_others_notice" => false}, - "role" => "member" - } - - assert represented == UserView.render("show.json", %{user: user, for: follower}) - end - - test "A user that follows you", %{user: user} do - follower = insert(:user) - {:ok, follower} = User.follow(follower, user) - {:ok, user} = User.update_follower_count(user) - image = "http://localhost:4001/images/avi.png" - banner = "http://localhost:4001/images/banner.png" - - represented = %{ - "id" => follower.id, - "name" => follower.name, - "screen_name" => follower.nickname, - "name_html" => follower.name, - "description" => HtmlSanitizeEx.strip_tags(follower.bio |> String.replace("<br>", "\n")), - "description_html" => HtmlSanitizeEx.basic_html(follower.bio), - "created_at" => follower.inserted_at |> Utils.format_naive_asctime(), - "favourites_count" => 0, - "statuses_count" => 0, - "friends_count" => 1, - "followers_count" => 0, - "profile_image_url" => image, - "profile_image_url_https" => image, - "profile_image_url_profile_size" => image, - "profile_image_url_original" => image, - "following" => false, - "follows_you" => true, - "statusnet_blocking" => false, - "statusnet_profile_url" => follower.ap_id, - "cover_photo" => banner, - "background_image" => nil, - "is_local" => true, - "locked" => false, - "hide_follows" => false, - "hide_followers" => false, - "fields" => [], - "pleroma" => %{ - "confirmation_pending" => false, - "tags" => [], - "skip_thread_containment" => false - }, - "rights" => %{"admin" => false, "delete_others_notice" => false}, - "role" => "member" - } - - assert represented == UserView.render("show.json", %{user: follower, for: user}) - end - - test "a user that is a moderator" do - user = insert(:user, %{info: %{is_moderator: true}}) - represented = UserView.render("show.json", %{user: user, for: user}) - - assert represented["rights"]["delete_others_notice"] - assert represented["role"] == "moderator" - end - - test "a user that is a admin" do - user = insert(:user, %{info: %{is_admin: true}}) - represented = UserView.render("show.json", %{user: user, for: user}) - - assert represented["rights"]["admin"] - assert represented["role"] == "admin" - end - - test "A moderator with hidden role for another user", %{user: user} do - admin = insert(:user, %{info: %{is_moderator: true, show_role: false}}) - represented = UserView.render("show.json", %{user: admin, for: user}) - - assert represented["role"] == nil - end - - test "An admin with hidden role for another user", %{user: user} do - admin = insert(:user, %{info: %{is_admin: true, show_role: false}}) - represented = UserView.render("show.json", %{user: admin, for: user}) - - assert represented["role"] == nil - end - - test "A regular user for the admin", %{user: user} do - admin = insert(:user, %{info: %{is_admin: true}}) - represented = UserView.render("show.json", %{user: user, for: admin}) - - assert represented["pleroma"]["deactivated"] == false - end - - test "A blocked user for the blocker" do - user = insert(:user) - blocker = insert(:user) - User.block(blocker, user) - image = "http://localhost:4001/images/avi.png" - banner = "http://localhost:4001/images/banner.png" - - represented = %{ - "id" => user.id, - "name" => user.name, - "screen_name" => user.nickname, - "name_html" => user.name, - "description" => HtmlSanitizeEx.strip_tags(user.bio |> String.replace("<br>", "\n")), - "description_html" => HtmlSanitizeEx.basic_html(user.bio), - "created_at" => user.inserted_at |> Utils.format_naive_asctime(), - "favourites_count" => 0, - "statuses_count" => 0, - "friends_count" => 0, - "followers_count" => 0, - "profile_image_url" => image, - "profile_image_url_https" => image, - "profile_image_url_profile_size" => image, - "profile_image_url_original" => image, - "following" => false, - "follows_you" => false, - "statusnet_blocking" => true, - "statusnet_profile_url" => user.ap_id, - "cover_photo" => banner, - "background_image" => nil, - "is_local" => true, - "locked" => false, - "hide_follows" => false, - "hide_followers" => false, - "fields" => [], - "pleroma" => %{ - "confirmation_pending" => false, - "tags" => [], - "skip_thread_containment" => false - }, - "rights" => %{"admin" => false, "delete_others_notice" => false}, - "role" => "member" - } - - blocker = User.get_cached_by_id(blocker.id) - assert represented == UserView.render("show.json", %{user: user, for: blocker}) - end - - test "a user with mastodon fields" do - fields = [ - %{ - "name" => "Pronouns", - "value" => "she/her" - }, - %{ - "name" => "Website", - "value" => "https://example.org/" - } - ] - - user = - insert(:user, %{ - info: %{ - source_data: %{ - "attachment" => - Enum.map(fields, fn field -> Map.put(field, "type", "PropertyValue") end) - } - } - }) - - userview = UserView.render("show.json", %{user: user}) - assert userview["fields"] == fields - end -end diff --git a/test/web/uploader_controller_test.exs b/test/web/uploader_controller_test.exs index 70028df1c..7c7f9a6ea 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-2019 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..4e5398c83 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-2019 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 a14ed3126..49cd1460b 100644 --- a/test/web/web_finger/web_finger_controller_test.exs +++ b/test/web/web_finger/web_finger_controller_test.exs @@ -1,22 +1,34 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 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 setup do mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end - config_path = [:instance, :federating] - initial_setting = Pleroma.Config.get(config_path) + clear_config_all([:instance, :federating]) do + Pleroma.Config.put([:instance, :federating], true) + end - Pleroma.Config.put(config_path, true) - on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end) - :ok + test "GET host-meta" do + response = + build_conn() + |> get("/.well-known/host-meta") + + assert response.status == 200 + + assert response.resp_body == + ~s(<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" template="#{ + Pleroma.Web.base_url() + }/.well-known/webfinger?resource={uri}" type="application/xrd+xml" /></XRD>) end test "Webfinger JRD" do @@ -30,6 +42,16 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do assert json_response(response, 200)["subject"] == "acct:#{user.nickname}@localhost" end + test "it returns 404 when user isn't found (JSON)" do + result = + build_conn() + |> put_req_header("accept", "application/jrd+json") + |> get("/.well-known/webfinger?resource=acct:jimm@localhost") + |> json_response(404) + + assert result == "Couldn't find user" + end + test "Webfinger XML" do user = insert(:user) @@ -41,6 +63,28 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do assert response(response, 200) end + test "it returns 404 when user isn't found (XML)" do + result = + build_conn() + |> put_req_header("accept", "application/xrd+xml") + |> get("/.well-known/webfinger?resource=acct:jimm@localhost") + |> response(404) + + assert result == "Couldn't find user" + end + + test "Sends a 404 when invalid format" do + user = insert(:user) + + 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 response = build_conn() diff --git a/test/web/web_finger/web_finger_test.exs b/test/web/web_finger/web_finger_test.exs index 0578b4b8e..5aa8c73cf 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-2019 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.WebFingerTest do @@ -40,17 +40,9 @@ defmodule Pleroma.Web.WebFingerTest do end describe "fingering" do - 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" + test "returns error when fails parse xml or json" do + user = "invalid_content@social.heldscal.la" + assert {:error, %Jason.DecodeError{}} = WebFinger.finger(user) end test "returns the ActivityPub actor URI for an ActivityPub user" do @@ -67,18 +59,18 @@ 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" + test "it work for AP-only user" do + user = "kpherox@mstdn.jp" {:ok, data} = WebFinger.finger(user) - assert data["magic_key"] == - "RSA.qfYaxztz7ZELrE4v5WpJrPM99SKI3iv9Y3Tw6nfLGk-4CRljNYqV8IYX2FXjeucC_DKhPNnlF6fXyASpcSmA_qupX9WC66eVhFhZ5OuyBOeLvJ1C4x7Hi7Di8MNBxY3VdQuQR0tTaS_YAZCwASKp7H6XEid3EJpGt0EQZoNzRd8=.AQAB" + assert data["magic_key"] == nil + assert data["salmon"] == nil - 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}" + assert data["topic"] == "https://mstdn.jp/users/kPherox.atom" + assert data["subject"] == "acct:kPherox@mstdn.jp" + assert data["ap_id"] == "https://mstdn.jp/users/kPherox" + assert data["subscribe_address"] == "https://mstdn.jp/authorize_interaction?acct={uri}" end test "it works for friendica" do diff --git a/test/web/websub/websub_controller_test.exs b/test/web/websub/websub_controller_test.exs deleted file mode 100644 index aa7262beb..000000000 --- a/test/web/websub/websub_controller_test.exs +++ /dev/null @@ -1,92 +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 - - setup_all do - config_path = [:instance, :federating] - initial_setting = Pleroma.Config.get(config_path) - - Pleroma.Config.put(config_path, true) - on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end) - - :ok - 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 |