diff options
Diffstat (limited to 'test/web')
71 files changed, 9532 insertions, 1313 deletions
diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index 30adfda36..77f5e39fa 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -11,13 +11,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ObjectView    alias Pleroma.Web.ActivityPub.UserView +  alias Pleroma.Web.ActivityPub.Utils +  alias Pleroma.Web.CommonAPI    setup_all do      Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) 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 @@ -34,8 +42,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        |> get(activity_pub_path(conn, :relay))        |> json_response(404)        |> assert +    end +  end + +  describe "/internal/fetch" do +    test "it returns the internal fetch user", %{conn: conn} do +      res = +        conn +        |> get(activity_pub_path(conn, :internal_fetch)) +        |> json_response(200) -      Pleroma.Config.put([:instance, :allow_relay], true) +      assert res["id"] =~ "/fetch"      end    end @@ -160,17 +177,65 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do    end    describe "/object/:uuid/likes" do -    test "it returns the like activities in a collection", %{conn: conn} do +    setup do        like = insert(:like_activity) -      uuid = String.split(like.data["object"], "/") |> List.last() +      like_object_ap_id = Object.normalize(like).data["id"] + +      uuid = +        like_object_ap_id +        |> String.split("/") +        |> List.last() +      [id: like.data["id"], uuid: uuid] +    end + +    test "it returns the like activities in a collection", %{conn: conn, id: id, uuid: uuid} do        result =          conn          |> put_req_header("accept", "application/activity+json")          |> get("/objects/#{uuid}/likes")          |> json_response(200) -      assert List.first(result["first"]["orderedItems"])["id"] == like.data["id"] +      assert List.first(result["first"]["orderedItems"])["id"] == id +      assert result["type"] == "OrderedCollection" +      assert result["totalItems"] == 1 +      refute result["first"]["next"] +    end + +    test "it does not crash when page number is exceeded total pages", %{conn: conn, uuid: uuid} do +      result = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/objects/#{uuid}/likes?page=2") +        |> json_response(200) + +      assert result["type"] == "OrderedCollectionPage" +      assert result["totalItems"] == 1 +      refute result["next"] +      assert Enum.empty?(result["orderedItems"]) +    end + +    test "it contains the next key when likes count is more than 10", %{conn: conn} do +      note = insert(:note_activity) +      insert_list(11, :like_activity, note_activity: note) + +      uuid = +        note +        |> Object.normalize() +        |> Map.get(:data) +        |> Map.get("id") +        |> String.split("/") +        |> List.last() + +      result = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/objects/#{uuid}/likes?page=1") +        |> json_response(200) + +      assert result["totalItems"] == 11 +      assert length(result["orderedItems"]) == 10 +      assert result["next"]      end    end @@ -234,13 +299,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do    end    describe "/users/:nickname/inbox" do -    test "it inserts an incoming activity into the database", %{conn: conn} do -      user = insert(:user) - +    setup do        data =          File.read!("test/fixtures/mastodon-post-activity.json")          |> Poison.decode!() -        |> Map.put("bcc", [user.ap_id]) + +      [data: data] +    end + +    test "it inserts an incoming activity into the database", %{conn: conn, data: data} do +      user = insert(:user) +      data = Map.put(data, "bcc", [user.ap_id])        conn =          conn @@ -253,16 +322,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        assert Activity.get_by_ap_id(data["id"])      end -    test "it accepts messages from actors that are followed by the user", %{conn: conn} do +    test "it accepts messages from actors that are followed by the user", %{ +      conn: conn, +      data: data +    } do        recipient = insert(:user)        actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})        {:ok, recipient} = User.follow(recipient, actor) -      data = -        File.read!("test/fixtures/mastodon-post-activity.json") -        |> Poison.decode!() -        object =          data["object"]          |> Map.put("attributedTo", actor.ap_id) @@ -298,6 +366,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do      test "it returns a note activity in a collection", %{conn: conn} do        note_activity = insert(:direct_note_activity) +      note_object = Object.normalize(note_activity)        user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))        conn = @@ -306,16 +375,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do          |> put_req_header("accept", "application/activity+json")          |> get("/users/#{user.nickname}/inbox") -      assert response(conn, 200) =~ note_activity.data["object"]["content"] +      assert response(conn, 200) =~ note_object.data["content"]      end -    test "it clears `unreachable` federation status of the sender", %{conn: conn} do +    test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do        user = insert(:user) - -      data = -        File.read!("test/fixtures/mastodon-post-activity.json") -        |> Poison.decode!() -        |> Map.put("bcc", [user.ap_id]) +      data = Map.put(data, "bcc", [user.ap_id])        sender_host = URI.parse(data["actor"]).host        Instances.set_consistently_unreachable(sender_host) @@ -330,6 +395,47 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        assert "ok" == json_response(conn, 200)        assert Instances.reachable?(sender_host)      end + +    test "it removes all follower collections but actor's", %{conn: conn} do +      [actor, recipient] = insert_pair(:user) + +      data = +        File.read!("test/fixtures/activitypub-client-post-activity.json") +        |> Poison.decode!() + +      object = Map.put(data["object"], "attributedTo", actor.ap_id) + +      data = +        data +        |> Map.put("id", Utils.generate_object_id()) +        |> Map.put("actor", actor.ap_id) +        |> Map.put("object", object) +        |> Map.put("cc", [ +          recipient.follower_address, +          actor.follower_address +        ]) +        |> Map.put("to", [ +          recipient.ap_id, +          recipient.follower_address, +          "https://www.w3.org/ns/activitystreams#Public" +        ]) + +      conn +      |> assign(:valid_signature, true) +      |> put_req_header("content-type", "application/activity+json") +      |> post("/users/#{recipient.nickname}/inbox", data) +      |> json_response(200) + +      activity = Activity.get_by_ap_id(data["id"]) + +      assert activity.id +      assert actor.follower_address in activity.recipients +      assert actor.follower_address in activity.data["cc"] + +      refute recipient.follower_address in activity.recipients +      refute recipient.follower_address in activity.data["cc"] +      refute recipient.follower_address in activity.data["to"] +    end    end    describe "/users/:nickname/outbox" do @@ -347,6 +453,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do      test "it returns a note activity in a collection", %{conn: conn} do        note_activity = insert(:note_activity) +      note_object = Object.normalize(note_activity)        user = User.get_cached_by_ap_id(note_activity.data["actor"])        conn = @@ -354,7 +461,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do          |> put_req_header("accept", "application/activity+json")          |> get("/users/#{user.nickname}/outbox") -      assert response(conn, 200) =~ note_activity.data["object"]["content"] +      assert response(conn, 200) =~ note_object.data["content"]      end      test "it returns an announce activity in a collection", %{conn: conn} do @@ -416,12 +523,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do      test "it erects a tombstone when receiving a delete activity", %{conn: conn} do        note_activity = insert(:note_activity) +      note_object = Object.normalize(note_activity)        user = User.get_cached_by_ap_id(note_activity.data["actor"])        data = %{          type: "Delete",          object: %{ -          id: note_activity.data["object"]["id"] +          id: note_object.data["id"]          }        } @@ -434,19 +542,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        result = json_response(conn, 201)        assert Activity.get_by_ap_id(result["id"]) -      object = Object.get_by_ap_id(note_activity.data["object"]["id"]) -      assert object +      assert object = Object.get_by_ap_id(note_object.data["id"])        assert object.data["type"] == "Tombstone"      end      test "it rejects delete activity of object from other actor", %{conn: conn} do        note_activity = insert(:note_activity) +      note_object = Object.normalize(note_activity)        user = insert(:user)        data = %{          type: "Delete",          object: %{ -          id: note_activity.data["object"]["id"] +          id: note_object.data["id"]          }        } @@ -461,12 +569,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do      test "it increases like count when receiving a like action", %{conn: conn} do        note_activity = insert(:note_activity) +      note_object = Object.normalize(note_activity)        user = User.get_cached_by_ap_id(note_activity.data["actor"])        data = %{          type: "Like",          object: %{ -          id: note_activity.data["object"]["id"] +          id: note_object.data["id"]          }        } @@ -479,8 +588,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        result = json_response(conn, 201)        assert Activity.get_by_ap_id(result["id"]) -      object = Object.get_by_ap_id(note_activity.data["object"]["id"]) -      assert object +      assert object = Object.get_by_ap_id(note_object.data["id"])        assert object.data["like_count"] == 1      end    end @@ -499,7 +607,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        assert result["first"]["orderedItems"] == [user.ap_id]      end -    test "it returns returns empty if the user has 'hide_followers' set", %{conn: conn} 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.follow(user, user_two) @@ -509,8 +617,35 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do          |> get("/users/#{user_two.nickname}/followers")          |> json_response(200) -      assert result["first"]["orderedItems"] == [] -      assert result["totalItems"] == 0 +      assert is_binary(result["first"]) +    end + +    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}}) + +      result = +        conn +        |> get("/users/#{user.nickname}/followers?page=1") + +      assert result.status == 403 +      assert result.resp_body == "" +    end + +    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}}) +      other_user = insert(:user) +      {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) + +      result = +        conn +        |> assign(:user, user) +        |> get("/users/#{user.nickname}/followers?page=1") +        |> json_response(200) + +      assert result["totalItems"] == 1 +      assert result["orderedItems"] == [other_user.ap_id]      end      test "it works for more than 10 users", %{conn: conn} do @@ -554,7 +689,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        assert result["first"]["orderedItems"] == [user_two.ap_id]      end -    test "it returns returns empty if the user has 'hide_follows' set", %{conn: conn} do +    test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do        user = insert(:user, %{info: %{hide_follows: true}})        user_two = insert(:user)        User.follow(user, user_two) @@ -564,8 +699,35 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do          |> get("/users/#{user.nickname}/following")          |> json_response(200) -      assert result["first"]["orderedItems"] == [] -      assert result["totalItems"] == 0 +      assert is_binary(result["first"]) +    end + +    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}}) + +      result = +        conn +        |> get("/users/#{user.nickname}/following?page=1") + +      assert result.status == 403 +      assert result.resp_body == "" +    end + +    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}}) +      other_user = insert(:user) +      {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user) + +      result = +        conn +        |> assign(:user, user) +        |> get("/users/#{user.nickname}/following?page=1") +        |> json_response(200) + +      assert result["totalItems"] == 1 +      assert result["orderedItems"] == [other_user.ap_id]      end      test "it works for more than 10 users", %{conn: conn} do diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 76586ee4a..1515f4eb6 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -6,11 +6,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do    use Pleroma.DataCase    alias Pleroma.Activity    alias Pleroma.Builders.ActivityBuilder -  alias Pleroma.Instances    alias Pleroma.Object    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub -  alias Pleroma.Web.ActivityPub.Publisher    alias Pleroma.Web.ActivityPub.Utils    alias Pleroma.Web.CommonAPI @@ -254,10 +252,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        }        {:ok, %Activity{} = activity} = ActivityPub.insert(data) -      object = Object.normalize(activity.data["object"]) - +      assert object = Object.normalize(activity)        assert is_binary(object.data["id"]) -      assert %Object{} = Object.get_by_ap_id(activity.data["object"])      end    end @@ -542,6 +538,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) @@ -659,7 +678,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do    describe "like an object" do      test "adds a like activity to the db" do        note_activity = insert(:note_activity) -      object = Object.get_by_ap_id(note_activity.data["object"]["id"]) +      assert object = Object.normalize(note_activity) +        user = insert(:user)        user_two = insert(:user) @@ -678,9 +698,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        assert like_activity == same_like_activity        assert object.data["likes"] == [user.ap_id] - -      [note_activity] = Activity.get_all_create_by_object_ap_id(object.data["id"]) -      assert note_activity.data["object"]["like_count"] == 1 +      assert object.data["like_count"] == 1        {:ok, _like_activity, object} = ActivityPub.like(user_two, object)        assert object.data["like_count"] == 2 @@ -690,7 +708,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do    describe "unliking" do      test "unliking a previously liked object" do        note_activity = insert(:note_activity) -      object = Object.get_by_ap_id(note_activity.data["object"]["id"]) +      object = Object.normalize(note_activity)        user = insert(:user)        # Unliking something that hasn't been liked does nothing @@ -710,7 +728,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do    describe "announcing an object" do      test "adds an announce activity to the db" do        note_activity = insert(:note_activity) -      object = Object.get_by_ap_id(note_activity.data["object"]["id"]) +      object = Object.normalize(note_activity)        user = insert(:user)        {:ok, announce_activity, object} = ActivityPub.announce(user, object) @@ -731,7 +749,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do    describe "unannouncing an object" do      test "unannouncing a previously announced object" do        note_activity = insert(:note_activity) -      object = Object.get_by_ap_id(note_activity.data["object"]["id"]) +      object = Object.normalize(note_activity)        user = insert(:user)        # Unannouncing an object that is not announced does nothing @@ -810,10 +828,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        assert activity.data["type"] == "Undo"        assert activity.data["actor"] == follower.ap_id -      assert is_map(activity.data["object"]) -      assert activity.data["object"]["type"] == "Follow" -      assert activity.data["object"]["object"] == followed.ap_id -      assert activity.data["object"]["id"] == follow_activity.data["id"] +      embedded_object = activity.data["object"] +      assert is_map(embedded_object) +      assert embedded_object["type"] == "Follow" +      assert embedded_object["object"] == followed.ap_id +      assert embedded_object["id"] == follow_activity.data["id"]      end    end @@ -839,22 +858,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        assert activity.data["type"] == "Undo"        assert activity.data["actor"] == blocker.ap_id -      assert is_map(activity.data["object"]) -      assert activity.data["object"]["type"] == "Block" -      assert activity.data["object"]["object"] == blocked.ap_id -      assert activity.data["object"]["id"] == block_activity.data["id"] +      embedded_object = activity.data["object"] +      assert is_map(embedded_object) +      assert embedded_object["type"] == "Block" +      assert embedded_object["object"] == blocked.ap_id +      assert embedded_object["id"] == block_activity.data["id"]      end    end    describe "deletion" do      test "it creates a delete activity and deletes the original object" do        note = insert(:note_activity) -      object = Object.get_by_ap_id(note.data["object"]["id"]) +      object = Object.normalize(note)        {:ok, delete} = ActivityPub.delete(object)        assert delete.data["type"] == "Delete"        assert delete.data["actor"] == note.data["actor"] -      assert delete.data["object"] == note.data["object"]["id"] +      assert delete.data["object"] == object.data["id"]        assert Activity.get_by_id(delete.id) != nil @@ -900,13 +920,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do        user = insert(:user)        note = insert(:note_activity) +      object = Object.normalize(note)        {:ok, object} = -        Object.get_by_ap_id(note.data["object"]["id"]) +        object          |> Object.change(%{            data: %{ -            "actor" => note.data["object"]["actor"], -            "id" => note.data["object"]["id"], +            "actor" => object.data["actor"], +            "id" => object.data["id"],              "to" => [user.ap_id],              "type" => "Note"            } @@ -1018,8 +1039,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        assert update.data["actor"] == user.ap_id        assert update.data["to"] == [user.follower_address] -      assert update.data["object"]["id"] == user_data["id"] -      assert update.data["object"]["type"] == user_data["type"] +      assert embedded_object = update.data["object"] +      assert embedded_object["id"] == user_data["id"] +      assert embedded_object["type"] == user_data["type"]      end    end @@ -1076,111 +1098,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do             } = 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)) -    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 {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) - -      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" +  test "fetch_activities/2 returns activities addressed to a list " do +    user = insert(:user) +    member = insert(:user) +    {:ok, list} = Pleroma.List.create("foo", user) +    {:ok, list} = Pleroma.List.follow(list, member) -      assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) +    {:ok, activity} = +      CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"}) -      refute called(Instances.set_unreachable(inbox)) -    end +    activity = Repo.preload(activity, :bookmark) +    activity = %Activity{activity | thread_muted?: !!activity.thread_muted?} -    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)) -    end +    assert ActivityPub.fetch_activities([], %{"user" => user}) == [activity]    end    def data_uri do @@ -1215,4 +1145,65 @@ 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, info} = ActivityPub.fetch_follow_information_for_user(user) +      assert info.hide_followers == true +      assert 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, info} = ActivityPub.fetch_follow_information_for_user(user) +      assert info.hide_followers == false +      assert info.hide_follows == true +    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 new file mode 100644 index 000000000..03dc299ec --- /dev/null +++ b/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs @@ -0,0 +1,145 @@ +# 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.AntiLinkSpamPolicyTest do +  use Pleroma.DataCase +  import Pleroma.Factory +  import ExUnit.CaptureLog + +  alias Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy + +  @linkless_message %{ +    "type" => "Create", +    "object" => %{ +      "content" => "hi world!" +    } +  } + +  @linkful_message %{ +    "type" => "Create", +    "object" => %{ +      "content" => "<a href='https://example.com'>hi world!</a>" +    } +  } + +  @response_message %{ +    "type" => "Create", +    "object" => %{ +      "name" => "yes", +      "type" => "Answer" +    } +  } + +  describe "with new user" do +    test "it allows posts without links" do +      user = insert(:user) + +      assert user.info.note_count == 0 + +      message = +        @linkless_message +        |> Map.put("actor", user.ap_id) + +      {:ok, _message} = AntiLinkSpamPolicy.filter(message) +    end + +    test "it disallows posts with links" do +      user = insert(:user) + +      assert user.info.note_count == 0 + +      message = +        @linkful_message +        |> Map.put("actor", user.ap_id) + +      {:reject, _} = AntiLinkSpamPolicy.filter(message) +    end +  end + +  describe "with old user" do +    test "it allows posts without links" do +      user = insert(:user, info: %{note_count: 1}) + +      assert user.info.note_count == 1 + +      message = +        @linkless_message +        |> Map.put("actor", user.ap_id) + +      {:ok, _message} = AntiLinkSpamPolicy.filter(message) +    end + +    test "it allows posts with links" do +      user = insert(:user, info: %{note_count: 1}) + +      assert user.info.note_count == 1 + +      message = +        @linkful_message +        |> Map.put("actor", user.ap_id) + +      {:ok, _message} = AntiLinkSpamPolicy.filter(message) +    end +  end + +  describe "with followed new user" do +    test "it allows posts without links" do +      user = insert(:user, info: %{follower_count: 1}) + +      assert user.info.follower_count == 1 + +      message = +        @linkless_message +        |> Map.put("actor", user.ap_id) + +      {:ok, _message} = AntiLinkSpamPolicy.filter(message) +    end + +    test "it allows posts with links" do +      user = insert(:user, info: %{follower_count: 1}) + +      assert user.info.follower_count == 1 + +      message = +        @linkful_message +        |> Map.put("actor", user.ap_id) + +      {:ok, _message} = AntiLinkSpamPolicy.filter(message) +    end +  end + +  describe "with unknown actors" do +    test "it rejects posts without links" do +      message = +        @linkless_message +        |> Map.put("actor", "http://invalid.actor") + +      assert capture_log(fn -> +               {:reject, _} = AntiLinkSpamPolicy.filter(message) +             end) =~ "[error] Could not decode user at fetch http://invalid.actor" +    end + +    test "it rejects posts with links" do +      message = +        @linkful_message +        |> Map.put("actor", "http://invalid.actor") + +      assert capture_log(fn -> +               {:reject, _} = AntiLinkSpamPolicy.filter(message) +             end) =~ "[error] Could not decode user at fetch http://invalid.actor" +    end +  end + +  describe "with contentless-objects" do +    test "it does not reject them or error out" do +      user = insert(:user, info: %{note_count: 1}) + +      message = +        @response_message +        |> Map.put("actor", user.ap_id) + +      {:ok, _message} = AntiLinkSpamPolicy.filter(message) +    end +  end +end diff --git a/test/web/activity_pub/mrf/ensure_re_prepended_test.exs b/test/web/activity_pub/mrf/ensure_re_prepended_test.exs new file mode 100644 index 000000000..dbc8b9e80 --- /dev/null +++ b/test/web/activity_pub/mrf/ensure_re_prepended_test.exs @@ -0,0 +1,82 @@ +# 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.EnsureRePrependedTest do +  use Pleroma.DataCase + +  alias Pleroma.Activity +  alias Pleroma.Object +  alias Pleroma.Web.ActivityPub.MRF.EnsureRePrepended + +  describe "rewrites summary" do +    test "it adds `re:` to summary object when child summary and parent summary equal" do +      message = %{ +        "type" => "Create", +        "object" => %{ +          "summary" => "object-summary", +          "inReplyTo" => %Activity{object: %Object{data: %{"summary" => "object-summary"}}} +        } +      } + +      assert {:ok, res} = EnsureRePrepended.filter(message) +      assert res["object"]["summary"] == "re: object-summary" +    end + +    test "it adds `re:` to summary object when child summary containts re-subject of parent summary " do +      message = %{ +        "type" => "Create", +        "object" => %{ +          "summary" => "object-summary", +          "inReplyTo" => %Activity{object: %Object{data: %{"summary" => "re: object-summary"}}} +        } +      } + +      assert {:ok, res} = EnsureRePrepended.filter(message) +      assert res["object"]["summary"] == "re: object-summary" +    end +  end + +  describe "skip filter" do +    test "it skip if type isn't 'Create'" do +      message = %{ +        "type" => "Annotation", +        "object" => %{"summary" => "object-summary"} +      } + +      assert {:ok, res} = EnsureRePrepended.filter(message) +      assert res == message +    end + +    test "it skip if summary is empty" do +      message = %{ +        "type" => "Create", +        "object" => %{ +          "inReplyTo" => %Activity{object: %Object{data: %{"summary" => "summary"}}} +        } +      } + +      assert {:ok, res} = EnsureRePrepended.filter(message) +      assert res == message +    end + +    test "it skip if inReplyTo is empty" do +      message = %{"type" => "Create", "object" => %{"summary" => "summary"}} +      assert {:ok, res} = EnsureRePrepended.filter(message) +      assert res == message +    end + +    test "it skip if parent and child summary isn't equal" do +      message = %{ +        "type" => "Create", +        "object" => %{ +          "summary" => "object-summary", +          "inReplyTo" => %Activity{object: %Object{data: %{"summary" => "summary"}}} +        } +      } + +      assert {:ok, res} = EnsureRePrepended.filter(message) +      assert res == message +    end +  end +end diff --git a/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs b/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs new file mode 100644 index 000000000..372e789be --- /dev/null +++ b/test/web/activity_pub/mrf/mediaproxy_warming_policy_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.ActivityPub.MRF.MediaProxyWarmingPolicyTest do +  use Pleroma.DataCase + +  alias Pleroma.HTTP +  alias Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy + +  import Mock + +  @message %{ +    "type" => "Create", +    "object" => %{ +      "type" => "Note", +      "content" => "content", +      "attachment" => [ +        %{"url" => [%{"href" => "http://example.com/image.jpg"}]} +      ] +    } +  } + +  test "it prefetches media proxy URIs" do +    with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do +      MediaProxyWarmingPolicy.filter(@message) +      assert called(HTTP.get(:_, :_, :_)) +    end +  end + +  test "it does nothing when no attachments are present" do +    object = +      @message["object"] +      |> Map.delete("attachment") + +    message = +      @message +      |> Map.put("object", object) + +    with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do +      MediaProxyWarmingPolicy.filter(message) +      refute called(HTTP.get(:_, :_, :_)) +    end +  end +end diff --git a/test/web/activity_pub/mrf/mention_policy_test.exs b/test/web/activity_pub/mrf/mention_policy_test.exs new file mode 100644 index 000000000..9fd9c31df --- /dev/null +++ b/test/web/activity_pub/mrf/mention_policy_test.exs @@ -0,0 +1,92 @@ +# 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.MentionPolicyTest do +  use Pleroma.DataCase + +  alias Pleroma.Web.ActivityPub.MRF.MentionPolicy + +  test "pass filter if allow list is empty" do +    Pleroma.Config.delete([:mrf_mention]) + +    message = %{ +      "type" => "Create", +      "to" => ["https://example.com/ok"], +      "cc" => ["https://example.com/blocked"] +    } + +    assert MentionPolicy.filter(message) == {:ok, message} +  end + +  describe "allow" do +    test "empty" do +      Pleroma.Config.put([:mrf_mention], %{actors: ["https://example.com/blocked"]}) + +      message = %{ +        "type" => "Create" +      } + +      assert MentionPolicy.filter(message) == {:ok, message} +    end + +    test "to" do +      Pleroma.Config.put([:mrf_mention], %{actors: ["https://example.com/blocked"]}) + +      message = %{ +        "type" => "Create", +        "to" => ["https://example.com/ok"] +      } + +      assert MentionPolicy.filter(message) == {:ok, message} +    end + +    test "cc" do +      Pleroma.Config.put([:mrf_mention], %{actors: ["https://example.com/blocked"]}) + +      message = %{ +        "type" => "Create", +        "cc" => ["https://example.com/ok"] +      } + +      assert MentionPolicy.filter(message) == {:ok, message} +    end + +    test "both" do +      Pleroma.Config.put([:mrf_mention], %{actors: ["https://example.com/blocked"]}) + +      message = %{ +        "type" => "Create", +        "to" => ["https://example.com/ok"], +        "cc" => ["https://example.com/ok2"] +      } + +      assert MentionPolicy.filter(message) == {:ok, message} +    end +  end + +  describe "deny" do +    test "to" do +      Pleroma.Config.put([:mrf_mention], %{actors: ["https://example.com/blocked"]}) + +      message = %{ +        "type" => "Create", +        "to" => ["https://example.com/blocked"] +      } + +      assert MentionPolicy.filter(message) == {:reject, nil} +    end + +    test "cc" do +      Pleroma.Config.put([:mrf_mention], %{actors: ["https://example.com/blocked"]}) + +      message = %{ +        "type" => "Create", +        "to" => ["https://example.com/ok"], +        "cc" => ["https://example.com/blocked"] +      } + +      assert MentionPolicy.filter(message) == {:reject, nil} +    end +  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/no_placeholder_text_policy_test.exs b/test/web/activity_pub/mrf/no_placeholder_text_policy_test.exs new file mode 100644 index 000000000..63ed71129 --- /dev/null +++ b/test/web/activity_pub/mrf/no_placeholder_text_policy_test.exs @@ -0,0 +1,37 @@ +# 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.NoPlaceholderTextPolicyTest do +  use Pleroma.DataCase +  alias Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy + +  test "it clears content object" do +    message = %{ +      "type" => "Create", +      "object" => %{"content" => ".", "attachment" => "image"} +    } + +    assert {:ok, res} = NoPlaceholderTextPolicy.filter(message) +    assert res["object"]["content"] == "" + +    message = put_in(message, ["object", "content"], "<p>.</p>") +    assert {:ok, res} = NoPlaceholderTextPolicy.filter(message) +    assert res["object"]["content"] == "" +  end + +  @messages [ +    %{ +      "type" => "Create", +      "object" => %{"content" => "test", "attachment" => "image"} +    }, +    %{"type" => "Create", "object" => %{"content" => "."}}, +    %{"type" => "Create", "object" => %{"content" => "<p>.</p>"}} +  ] +  test "it skips filter" do +    Enum.each(@messages, fn message -> +      assert {:ok, res} = NoPlaceholderTextPolicy.filter(message) +      assert res == message +    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 new file mode 100644 index 000000000..3916a1f35 --- /dev/null +++ b/test/web/activity_pub/mrf/normalize_markup_test.exs @@ -0,0 +1,42 @@ +# 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.NormalizeMarkupTest do +  use Pleroma.DataCase +  alias Pleroma.Web.ActivityPub.MRF.NormalizeMarkup + +  @html_sample """ +  <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/" rel="tag noallowed">example.com</a> +  this is an image: <img src="http://example.com/image.jpg"><br /> +  <script>alert('hacked')</script> +  """ + +  test "it filter html tags" 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') +    """ + +    message = %{"type" => "Create", "object" => %{"content" => @html_sample}} + +    assert {:ok, res} = NormalizeMarkup.filter(message) +    assert res["object"]["content"] == expected +  end + +  test "it skips filter if type isn't `Create`" do +    message = %{"type" => "Note", "object" => %{}} + +    assert {:ok, res} = NormalizeMarkup.filter(message) +    assert res == message +  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 new file mode 100644 index 000000000..fc1d190bb --- /dev/null +++ b/test/web/activity_pub/mrf/reject_non_public_test.exs @@ -0,0 +1,100 @@ +# 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.RejectNonPublicTest do +  use Pleroma.DataCase +  import Pleroma.Factory + +  alias Pleroma.Web.ActivityPub.MRF.RejectNonPublic + +  clear_config([:mrf_rejectnonpublic]) + +  describe "public message" do +    test "it's allowed when address is public" do +      actor = insert(:user, follower_address: "test-address") + +      message = %{ +        "actor" => actor.ap_id, +        "to" => ["https://www.w3.org/ns/activitystreams#Public"], +        "cc" => ["https://www.w3.org/ns/activitystreams#Publid"], +        "type" => "Create" +      } + +      assert {:ok, message} = RejectNonPublic.filter(message) +    end + +    test "it's allowed when cc address contain public address" do +      actor = insert(:user, follower_address: "test-address") + +      message = %{ +        "actor" => actor.ap_id, +        "to" => ["https://www.w3.org/ns/activitystreams#Public"], +        "cc" => ["https://www.w3.org/ns/activitystreams#Publid"], +        "type" => "Create" +      } + +      assert {:ok, message} = RejectNonPublic.filter(message) +    end +  end + +  describe "followers message" do +    test "it's allowed when addrer of message in the follower addresses of user and it enabled in config" do +      actor = insert(:user, follower_address: "test-address") + +      message = %{ +        "actor" => actor.ap_id, +        "to" => ["test-address"], +        "cc" => ["https://www.w3.org/ns/activitystreams#Publid"], +        "type" => "Create" +      } + +      Pleroma.Config.put([:mrf_rejectnonpublic, :allow_followersonly], true) +      assert {:ok, message} = RejectNonPublic.filter(message) +    end + +    test "it's rejected when addrer of message in the follower addresses of user and it disabled in config" do +      actor = insert(:user, follower_address: "test-address") + +      message = %{ +        "actor" => actor.ap_id, +        "to" => ["test-address"], +        "cc" => ["https://www.w3.org/ns/activitystreams#Publid"], +        "type" => "Create" +      } + +      Pleroma.Config.put([:mrf_rejectnonpublic, :allow_followersonly], false) +      assert {:reject, nil} = RejectNonPublic.filter(message) +    end +  end + +  describe "direct message" do +    test "it's allows when direct messages are allow" do +      actor = insert(:user) + +      message = %{ +        "actor" => actor.ap_id, +        "to" => ["https://www.w3.org/ns/activitystreams#Publid"], +        "cc" => ["https://www.w3.org/ns/activitystreams#Publid"], +        "type" => "Create" +      } + +      Pleroma.Config.put([:mrf_rejectnonpublic, :allow_direct], true) +      assert {:ok, message} = RejectNonPublic.filter(message) +    end + +    test "it's reject when direct messages aren't allow" do +      actor = insert(:user) + +      message = %{ +        "actor" => actor.ap_id, +        "to" => ["https://www.w3.org/ns/activitystreams#Publid~~~"], +        "cc" => ["https://www.w3.org/ns/activitystreams#Publid"], +        "type" => "Create" +      } + +      Pleroma.Config.put([:mrf_rejectnonpublic, :allow_direct], false) +      assert {:reject, nil} = RejectNonPublic.filter(message) +    end +  end +end diff --git a/test/web/activity_pub/mrf/simple_policy_test.exs b/test/web/activity_pub/mrf/simple_policy_test.exs index 0fd68e103..7203b27da 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() @@ -192,6 +243,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do        assert SimplePolicy.filter(remote_message) == {:reject, nil}      end + +    test "match with wildcard domain" do +      Config.put([:mrf_simple, :reject], ["*.remote.instance"]) + +      remote_message = build_remote_message() + +      assert SimplePolicy.filter(remote_message) == {:reject, nil} +    end    end    describe "when :accept" do @@ -224,6 +283,16 @@ 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 "match 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    end    describe "when :avatar_removal" do @@ -251,6 +320,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 +356,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/subchain_policy_test.exs b/test/web/activity_pub/mrf/subchain_policy_test.exs new file mode 100644 index 000000000..f7cbcad48 --- /dev/null +++ b/test/web/activity_pub/mrf/subchain_policy_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.ActivityPub.MRF.SubchainPolicyTest do +  use Pleroma.DataCase + +  alias Pleroma.Web.ActivityPub.MRF.DropPolicy +  alias Pleroma.Web.ActivityPub.MRF.SubchainPolicy + +  @message %{ +    "actor" => "https://banned.com", +    "type" => "Create", +    "object" => %{"content" => "hi"} +  } + +  test "it matches and processes subchains when the actor matches a configured target" do +    Pleroma.Config.put([:mrf_subchain, :match_actor], %{ +      ~r/^https:\/\/banned.com/s => [DropPolicy] +    }) + +    {:reject, _} = SubchainPolicy.filter(@message) +  end + +  test "it doesn't match and process subchains when the actor doesn't match a configured target" do +    Pleroma.Config.put([:mrf_subchain, :match_actor], %{ +      ~r/^https:\/\/borked.com/s => [DropPolicy] +    }) + +    {:ok, _message} = SubchainPolicy.filter(@message) +  end +end diff --git a/test/web/activity_pub/mrf/tag_policy_test.exs b/test/web/activity_pub/mrf/tag_policy_test.exs new file mode 100644 index 000000000..4aa35311e --- /dev/null +++ b/test/web/activity_pub/mrf/tag_policy_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.ActivityPub.MRF.TagPolicyTest do +  use Pleroma.DataCase +  import Pleroma.Factory + +  alias Pleroma.Web.ActivityPub.MRF.TagPolicy +  @public "https://www.w3.org/ns/activitystreams#Public" + +  describe "mrf_tag:disable-any-subscription" do +    test "rejects message" do +      actor = insert(:user, tags: ["mrf_tag:disable-any-subscription"]) +      message = %{"object" => actor.ap_id, "type" => "Follow"} +      assert {:reject, nil} = TagPolicy.filter(message) +    end +  end + +  describe "mrf_tag:disable-remote-subscription" do +    test "rejects non-local follow requests" do +      actor = insert(:user, tags: ["mrf_tag:disable-remote-subscription"]) +      follower = insert(:user, tags: ["mrf_tag:disable-remote-subscription"], local: false) +      message = %{"object" => actor.ap_id, "type" => "Follow", "actor" => follower.ap_id} +      assert {:reject, nil} = TagPolicy.filter(message) +    end + +    test "allows non-local follow requests" do +      actor = insert(:user, tags: ["mrf_tag:disable-remote-subscription"]) +      follower = insert(:user, tags: ["mrf_tag:disable-remote-subscription"], local: true) +      message = %{"object" => actor.ap_id, "type" => "Follow", "actor" => follower.ap_id} +      assert {:ok, message} = TagPolicy.filter(message) +    end +  end + +  describe "mrf_tag:sandbox" do +    test "removes from public timelines" do +      actor = insert(:user, tags: ["mrf_tag:sandbox"]) + +      message = %{ +        "actor" => actor.ap_id, +        "type" => "Create", +        "object" => %{}, +        "to" => [@public, "f"], +        "cc" => [@public, "d"] +      } + +      except_message = %{ +        "actor" => actor.ap_id, +        "type" => "Create", +        "object" => %{"to" => ["f", actor.follower_address], "cc" => ["d"]}, +        "to" => ["f", actor.follower_address], +        "cc" => ["d"] +      } + +      assert TagPolicy.filter(message) == {:ok, except_message} +    end +  end + +  describe "mrf_tag:force-unlisted" do +    test "removes from the federated timeline" do +      actor = insert(:user, tags: ["mrf_tag:force-unlisted"]) + +      message = %{ +        "actor" => actor.ap_id, +        "type" => "Create", +        "object" => %{}, +        "to" => [@public, "f"], +        "cc" => [actor.follower_address, "d"] +      } + +      except_message = %{ +        "actor" => actor.ap_id, +        "type" => "Create", +        "object" => %{"to" => ["f", actor.follower_address], "cc" => ["d", @public]}, +        "to" => ["f", actor.follower_address], +        "cc" => ["d", @public] +      } + +      assert TagPolicy.filter(message) == {:ok, except_message} +    end +  end + +  describe "mrf_tag:media-strip" do +    test "removes attachments" do +      actor = insert(:user, tags: ["mrf_tag:media-strip"]) + +      message = %{ +        "actor" => actor.ap_id, +        "type" => "Create", +        "object" => %{"attachment" => ["file1"]} +      } + +      except_message = %{ +        "actor" => actor.ap_id, +        "type" => "Create", +        "object" => %{} +      } + +      assert TagPolicy.filter(message) == {:ok, except_message} +    end +  end + +  describe "mrf_tag:media-force-nsfw" do +    test "Mark as sensitive on presence of attachments" do +      actor = insert(:user, tags: ["mrf_tag:media-force-nsfw"]) + +      message = %{ +        "actor" => actor.ap_id, +        "type" => "Create", +        "object" => %{"tag" => ["test"], "attachment" => ["file1"]} +      } + +      except_message = %{ +        "actor" => actor.ap_id, +        "type" => "Create", +        "object" => %{"tag" => ["test", "nsfw"], "attachment" => ["file1"], "sensitive" => true} +      } + +      assert TagPolicy.filter(message) == {:ok, except_message} +    end +  end +end diff --git a/test/web/activity_pub/mrf/user_allowlist_policy_test.exs b/test/web/activity_pub/mrf/user_allowlist_policy_test.exs new file mode 100644 index 000000000..72084c0fd --- /dev/null +++ b/test/web/activity_pub/mrf/user_allowlist_policy_test.exs @@ -0,0 +1,31 @@ +# 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.UserAllowListPolicyTest do +  use Pleroma.DataCase +  import Pleroma.Factory + +  alias Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy + +  clear_config([:mrf_user_allowlist, :localhost]) + +  test "pass filter if allow list is empty" do +    actor = insert(:user) +    message = %{"actor" => actor.ap_id} +    assert UserAllowListPolicy.filter(message) == {:ok, message} +  end + +  test "pass filter if allow list isn't empty and user in allow list" do +    actor = insert(:user) +    Pleroma.Config.put([:mrf_user_allowlist, :localhost], [actor.ap_id, "test-ap-id"]) +    message = %{"actor" => actor.ap_id} +    assert UserAllowListPolicy.filter(message) == {:ok, message} +  end + +  test "rejected if allow list isn't empty and user not in allow list" do +    actor = insert(:user) +    Pleroma.Config.put([:mrf_user_allowlist, :localhost], ["test-ap-id"]) +    message = %{"actor" => actor.ap_id} +    assert UserAllowListPolicy.filter(message) == {:reject, nil} +  end +end 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..36a39c84c --- /dev/null +++ b/test/web/activity_pub/publisher_test.exs @@ -0,0 +1,266 @@ +# 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.DataCase + +  import Pleroma.Factory +  import Tesla.Mock +  import Mock + +  alias Pleroma.Activity +  alias Pleroma.Instances +  alias Pleroma.Web.ActivityPub.Publisher + +  @as_public "https://www.w3.org/ns/activitystreams#Public" + +  setup do +    mock(fn env -> apply(HttpRequestMock, :request, [env]) end) +    :ok +  end + +  describe "determine_inbox/2" do +    test "it returns sharedInbox for messages involving as:Public in to" do +      user = +        insert(:user, %{ +          info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}} +        }) + +      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, %{ +          info: %{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, %{ +          info: %{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, %{ +          info: %{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, %{ +          info: %{ +            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, %{ +          info: %{ +            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 {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) + +      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 {:error, _} = +               Publisher.publish_one(%{ +                 inbox: inbox, +                 json: "{}", +                 actor: actor, +                 id: 1, +                 unreachable_since: NaiveDateTime.utc_now() +               }) + +      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, +          info: %{ +            ap_enabled: true, +            source_data: %{"inbox" => "https://domain.com/users/nick1/inbox"} +          } +        ) + +      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: actor, +                 id: note_activity.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..e10b808f7 100644 --- a/test/web/activity_pub/relay_test.exs +++ b/test/web/activity_pub/relay_test.exs @@ -5,11 +5,71 @@  defmodule Pleroma.Web.ActivityPub.RelayTest do    use Pleroma.DataCase +  alias Pleroma.Activity +  alias Pleroma.Object +  alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Relay +  import Pleroma.Factory +    test "gets an actor for the relay" do      user = Relay.get_actor() +    assert user.ap_id == "#{Pleroma.Web.Endpoint.url()}/relay" +  end + +  describe "follow/1" do +    test "returns errors when user not found" do +      assert Relay.follow("test-ap-id") == {:error, "Could not fetch by AP id"} +    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 Relay.unfollow("test-ap-id") == {:error, "Could not fetch by AP id"} +    end + +    test "returns activity" do +      user = insert(:user) +      service_actor = Relay.get_actor() +      ActivityPub.follow(service_actor, user) +      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] +    end +  end + +  describe "publish/1" do +    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 -    assert user.ap_id =~ "/relay" +    test "returns announce activity" do +      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"] +    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 new file mode 100644 index 000000000..857d65564 --- /dev/null +++ b/test/web/activity_pub/transmogrifier/follow_handling_test.exs @@ -0,0 +1,143 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do +  use Pleroma.DataCase +  alias Pleroma.Activity +  alias Pleroma.Repo +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.Transmogrifier +  alias Pleroma.Web.ActivityPub.Utils + +  import Pleroma.Factory +  import Ecto.Query + +  setup_all do +    Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) +    :ok +  end + +  describe "handle_incoming" do +    test "it works for incoming follow requests" do +      user = insert(:user) + +      data = +        File.read!("test/fixtures/mastodon-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"] == "http://mastodon.example.org/users/admin" +      assert data["type"] == "Follow" +      assert data["id"] == "http://mastodon.example.org/users/admin#follows/2" + +      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 "with locked accounts, it does not create a follow or an accept" do +      user = insert(:user, info: %{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["state"] == "pending" + +      refute User.following?(User.get_cached_by_ap_id(data["actor"]), user) + +      accepts = +        from( +          a in Activity, +          where: fragment("?->>'type' = ?", a.data, "Accept") +        ) +        |> Repo.all() + +      assert length(accepts) == 0 +    end + +    test "it works for follow requests when you are already followed, creating a new accept activity" do +      # This is important because the remote might have the wrong idea about the +      # current follow status. This can lead to instance A thinking that x@A is +      # followed by y@B, but B thinks they are not. In this case, the follow can +      # never go through again because it will never get an Accept. +      user = insert(:user) + +      data = +        File.read!("test/fixtures/mastodon-follow-activity.json") +        |> Poison.decode!() +        |> Map.put("object", user.ap_id) + +      {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data) + +      accepts = +        from( +          a in Activity, +          where: fragment("?->>'type' = ?", a.data, "Accept") +        ) +        |> Repo.all() + +      assert length(accepts) == 1 + +      data = +        File.read!("test/fixtures/mastodon-follow-activity.json") +        |> Poison.decode!() +        |> Map.put("id", String.replace(data["id"], "2", "3")) +        |> Map.put("object", user.ap_id) + +      {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data) + +      accepts = +        from( +          a in Activity, +          where: fragment("?->>'type' = ?", a.data, "Accept") +        ) +        |> Repo.all() + +      assert length(accepts) == 2 +    end + +    test "it rejects incoming follow requests from blocked users when deny_follow_blocked is enabled" do +      Pleroma.Config.put([:user, :deny_follow_blocked], true) + +      user = insert(:user) +      {:ok, target} = User.get_or_fetch("http://mastodon.example.org/users/admin") + +      {:ok, user} = User.block(user, target) + +      data = +        File.read!("test/fixtures/mastodon-follow-activity.json") +        |> Poison.decode!() +        |> Map.put("object", user.ap_id) + +      {:ok, %Activity{data: %{"id" => id}}} = Transmogrifier.handle_incoming(data) + +      %Activity{} = activity = Activity.get_by_ap_id(id) + +      assert activity.data["state"] == "reject" +    end + +    test "it works for incoming follow requests from hubzilla" do +      user = insert(:user) + +      data = +        File.read!("test/fixtures/hubzilla-follow-activity.json") +        |> Poison.decode!() +        |> Map.put("object", user.ap_id) +        |> Utils.normalize_params() + +      {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + +      assert data["actor"] == "https://hubzilla.example.org/channel/kaniini" +      assert data["type"] == "Follow" +      assert data["id"] == "https://hubzilla.example.org/channel/kaniini#follows/2" +      assert User.following?(User.get_cached_by_ap_id(data["actor"]), user) +    end +  end +end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index ee71de8d0..629c76c97 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -11,18 +11,21 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Transmogrifier -  alias Pleroma.Web.ActivityPub.Utils +  alias Pleroma.Web.CommonAPI    alias Pleroma.Web.OStatus    alias Pleroma.Web.Websub.WebsubClientSubscription +  import Mock    import Pleroma.Factory -  alias Pleroma.Web.CommonAPI +  import ExUnit.CaptureLog    setup_all do      Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)      :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) @@ -30,7 +33,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        data =          File.read!("test/fixtures/mastodon-post-activity.json")          |> Poison.decode!() -        |> Map.put("object", activity.data["object"]) +        |> Map.put("object", Object.normalize(activity).data)        {:ok, returned_activity} = Transmogrifier.handle_incoming(data) @@ -46,12 +49,9 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do          data["object"]          |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873") -      data = -        data -        |> Map.put("object", object) - +      data = Map.put(data, "object", object)        {:ok, returned_activity} = Transmogrifier.handle_incoming(data) -      returned_object = Object.normalize(returned_activity.data["object"]) +      returned_object = Object.normalize(returned_activity, false)        assert activity =                 Activity.get_create_by_object_ap_id( @@ -61,6 +61,50 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"      end +    test "it does not fetch replied-to activities beyond max_replies_depth" do +      data = +        File.read!("test/fixtures/mastodon-post-activity.json") +        |> Poison.decode!() + +      object = +        data["object"] +        |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873") + +      data = Map.put(data, "object", object) + +      with_mock Pleroma.Web.Federator, +        allowed_incoming_reply_depth?: fn _ -> false end do +        {:ok, returned_activity} = Transmogrifier.handle_incoming(data) + +        returned_object = Object.normalize(returned_activity, false) + +        refute Activity.get_create_by_object_ap_id( +                 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment" +               ) + +        assert returned_object.data["inReplyToAtomUri"] == +                 "https://shitposter.club/notice/2827873" +      end +    end + +    test "it does not crash if the object in inReplyTo can't be fetched" do +      data = +        File.read!("test/fixtures/mastodon-post-activity.json") +        |> Poison.decode!() + +      object = +        data["object"] +        |> Map.put("inReplyTo", "https://404.site/whatever") + +      data = +        data +        |> Map.put("object", object) + +      assert capture_log(fn -> +               {:ok, _returned_activity} = Transmogrifier.handle_incoming(data) +             end) =~ "[error] Couldn't fetch \"\"https://404.site/whatever\"\", error: nil" +    end +      test "it works for incoming notices" do        data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() @@ -81,25 +125,27 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert data["actor"] == "http://mastodon.example.org/users/admin" -      object = Object.normalize(data["object"]).data -      assert object["id"] == "http://mastodon.example.org/users/admin/statuses/99512778738411822" +      object_data = Object.normalize(data["object"]).data + +      assert object_data["id"] == +               "http://mastodon.example.org/users/admin/statuses/99512778738411822" -      assert object["to"] == ["https://www.w3.org/ns/activitystreams#Public"] +      assert object_data["to"] == ["https://www.w3.org/ns/activitystreams#Public"] -      assert object["cc"] == [ +      assert object_data["cc"] == [                 "http://mastodon.example.org/users/admin/followers",                 "http://localtesting.pleroma.lol/users/lain"               ] -      assert object["actor"] == "http://mastodon.example.org/users/admin" -      assert object["attributedTo"] == "http://mastodon.example.org/users/admin" +      assert object_data["actor"] == "http://mastodon.example.org/users/admin" +      assert object_data["attributedTo"] == "http://mastodon.example.org/users/admin" -      assert object["context"] == +      assert object_data["context"] ==                 "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation" -      assert object["sensitive"] == true +      assert object_data["sensitive"] == true -      user = User.get_cached_by_ap_id(object["actor"]) +      user = User.get_cached_by_ap_id(object_data["actor"])        assert user.info.note_count == 1      end @@ -113,6 +159,55 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert Enum.at(object.data["tag"], 2) == "moo"      end +    test "it works for incoming questions" do +      data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!() + +      {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) + +      object = Object.normalize(activity) + +      assert Enum.all?(object.data["oneOf"], fn choice -> +               choice["name"] in [ +                 "Dunno", +                 "Everyone knows that!", +                 "25 char limit is dumb", +                 "I can't even fit a funny" +               ] +             end) +    end + +    test "it rewrites Note votes to Answers and increments vote counters on question activities" do +      user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "suya...", +          "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10} +        }) + +      object = Object.normalize(activity) + +      data = +        File.read!("test/fixtures/mastodon-vote.json") +        |> Poison.decode!() +        |> Kernel.put_in(["to"], user.ap_id) +        |> Kernel.put_in(["object", "inReplyTo"], object.data["id"]) +        |> Kernel.put_in(["object", "to"], user.ap_id) + +      {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) +      answer_object = Object.normalize(activity) +      assert answer_object.data["type"] == "Answer" +      object = Object.get_by_ap_id(object.data["id"]) + +      assert Enum.any?( +               object.data["oneOf"], +               fn +                 %{"name" => "suya..", "replies" => %{"totalItems" => 1}} -> true +                 _ -> false +               end +             ) +    end +      test "it works for incoming notices with contentMap" do        data =          File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Poison.decode!() @@ -199,59 +294,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert object_data["cc"] == to      end -    test "it works for incoming follow requests" do -      user = insert(:user) - -      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["actor"] == "http://mastodon.example.org/users/admin" -      assert data["type"] == "Follow" -      assert data["id"] == "http://mastodon.example.org/users/admin#follows/2" -      assert User.following?(User.get_cached_by_ap_id(data["actor"]), user) -    end - -    test "it rejects incoming follow requests from blocked users when deny_follow_blocked is enabled" do -      Pleroma.Config.put([:user, :deny_follow_blocked], true) - -      user = insert(:user) -      {:ok, target} = User.get_or_fetch("http://mastodon.example.org/users/admin") - -      {:ok, user} = User.block(user, target) - -      data = -        File.read!("test/fixtures/mastodon-follow-activity.json") -        |> Poison.decode!() -        |> Map.put("object", user.ap_id) - -      {:ok, %Activity{data: %{"id" => id}}} = Transmogrifier.handle_incoming(data) - -      %Activity{} = activity = Activity.get_by_ap_id(id) - -      assert activity.data["state"] == "reject" -    end - -    test "it works for incoming follow requests from hubzilla" do -      user = insert(:user) - -      data = -        File.read!("test/fixtures/hubzilla-follow-activity.json") -        |> Poison.decode!() -        |> Map.put("object", user.ap_id) -        |> Utils.normalize_params() - -      {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - -      assert data["actor"] == "https://hubzilla.example.org/channel/kaniini" -      assert data["type"] == "Follow" -      assert data["id"] == "https://hubzilla.example.org/channel/kaniini#follows/2" -      assert User.following?(User.get_cached_by_ap_id(data["actor"]), user) -    end -      test "it works for incoming likes" do        user = insert(:user)        {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) @@ -376,6 +418,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do          |> Map.put("attributedTo", user.ap_id)          |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])          |> Map.put("cc", []) +        |> Map.put("id", user.ap_id <> "/activities/12345678")        data = Map.put(data, "object", object) @@ -399,6 +442,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do          |> Map.put("attributedTo", user.ap_id)          |> Map.put("to", nil)          |> Map.put("cc", nil) +        |> Map.put("id", user.ap_id <> "/activities/12345678")        data = Map.put(data, "object", object) @@ -408,6 +452,27 @@ 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 works for incoming update activities" do        data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() @@ -446,6 +511,60 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert user.bio == "<p>Some bio</p>"      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.Info.fields(user.info) == [ +               %{"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.Info.fields(user.info) == [ +               %{"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.Info.fields(user.info) == [ +               %{"name" => "foo", "value" => "updated"}, +               %{"name" => "foo1", "value" => "updated"} +             ] +    end +      test "it works for incoming update activities which lock the account" do        data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() @@ -505,11 +624,38 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do          data          |> Map.put("object", object) -      :error = Transmogrifier.handle_incoming(data) +      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}}"        assert Activity.get_by_id(activity.id)      end +    test "it works for incoming user deletes" do +      %{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin") + +      data = +        File.read!("test/fixtures/mastodon-delete-user.json") +        |> Poison.decode!() + +      {:ok, _} = Transmogrifier.handle_incoming(data) + +      refute User.get_cached_by_ap_id(ap_id) +    end + +    test "it fails for incoming user deletes with spoofed origin" do +      %{ap_id: ap_id} = insert(:user) + +      data = +        File.read!("test/fixtures/mastodon-delete-user.json") +        |> Poison.decode!() +        |> Map.put("actor", ap_id) + +      assert :error == Transmogrifier.handle_incoming(data) +      assert User.get_cached_by_ap_id(ap_id) +    end +      test "it works for incoming unannounces with an existing notice" do        user = insert(:user)        {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"}) @@ -531,10 +677,11 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)        assert data["type"] == "Undo" -      assert data["object"]["type"] == "Announce" -      assert data["object"]["object"] == activity.data["object"] +      assert object_data = data["object"] +      assert object_data["type"] == "Announce" +      assert object_data["object"] == activity.data["object"] -      assert data["object"]["id"] == +      assert object_data["id"] ==                 "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"      end @@ -844,7 +991,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        other_user = insert(:user)        {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) -      object = Object.normalize(activity.data["object"]) +      object = Object.normalize(activity)        message = %{          "@context" => "https://www.w3.org/ns/activitystreams", @@ -991,14 +1138,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 @@ -1028,6 +1168,18 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert modified["directMessage"] == true      end + +    test "it strips BCC field" do +      user = insert(:user) +      {:ok, list} = Pleroma.List.create("foo", user) + +      {:ok, activity} = +        CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"}) + +      {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + +      assert is_nil(modified["bcc"]) +    end    end    describe "user upgrade" do @@ -1053,6 +1205,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert user.info.ap_enabled        assert user.info.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 @@ -1210,10 +1363,37 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do      end    end +  test "Rewrites Answers to Notes" do +    user = insert(:user) + +    {:ok, poll_activity} = +      CommonAPI.post(user, %{ +        "status" => "suya...", +        "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10} +      }) + +    poll_object = Object.normalize(poll_activity) +    # TODO: Replace with CommonAPI vote creation when implemented +    data = +      File.read!("test/fixtures/mastodon-vote.json") +      |> Poison.decode!() +      |> Kernel.put_in(["to"], user.ap_id) +      |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"]) +      |> Kernel.put_in(["object", "to"], user.ap_id) + +    {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) +    {:ok, data} = Transmogrifier.prepare_outgoing(activity.data) + +    assert data["object"]["type"] == "Note" +  end +    describe "fix_explicit_addressing" do -    test "moves non-explicitly mentioned actors to cc" do +    setup do        user = insert(:user) +      [user: user] +    end +    test "moves non-explicitly mentioned actors to cc", %{user: user} do        explicitly_mentioned_actors = [          "https://pleroma.gold/users/user1",          "https://pleroma.gold/user2" @@ -1235,9 +1415,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]      end -    test "does not move actor's follower collection to cc" do -      user = insert(:user) - +    test "does not move actor's follower collection to cc", %{user: user} do        object = %{          "actor" => user.ap_id,          "to" => [user.follower_address], @@ -1248,5 +1426,21 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert user.follower_address in fixed_object["to"]        refute user.follower_address in fixed_object["cc"]      end + +    test "removes recipient's follower collection from cc", %{user: user} do +      recipient = insert(:user) + +      object = %{ +        "actor" => user.ap_id, +        "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"], +        "cc" => [user.follower_address, recipient.follower_address] +      } + +      fixed_object = Transmogrifier.fix_explicit_addressing(object) + +      assert user.follower_address in fixed_object["cc"] +      refute recipient.follower_address in fixed_object["cc"] +      refute recipient.follower_address in fixed_object["to"] +    end    end  end diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs index c57fae437..ca5f057a7 100644 --- a/test/web/activity_pub/utils_test.exs +++ b/test/web/activity_pub/utils_test.exs @@ -1,6 +1,12 @@ +# 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.UtilsTest do    use Pleroma.DataCase    alias Pleroma.Activity +  alias Pleroma.Object +  alias Pleroma.Repo    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Utils @@ -204,4 +210,93 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do               ]             }    end + +  describe "get_existing_votes" do +    test "fetches existing votes" do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "How do I pronounce LaTeX?", +          "poll" => %{ +            "options" => ["laytekh", "lahtekh", "latex"], +            "expires_in" => 20, +            "multiple" => true +          } +        }) + +      object = Object.normalize(activity) +      {:ok, votes, object} = CommonAPI.vote(other_user, object, [0, 1]) +      assert Enum.sort(Utils.get_existing_votes(other_user.ap_id, object)) == Enum.sort(votes) +    end + +    test "fetches only Create activities" do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "Are we living in a society?", +          "poll" => %{ +            "options" => ["yes", "no"], +            "expires_in" => 20 +          } +        }) + +      object = Object.normalize(activity) +      {:ok, [vote], object} = CommonAPI.vote(other_user, object, [0]) +      vote_object = Object.normalize(vote) +      {:ok, _activity, _object} = ActivityPub.like(user, vote_object) +      [fetched_vote] = Utils.get_existing_votes(other_user.ap_id, object) +      assert fetched_vote.id == vote.id +    end +  end + +  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}) +      follower = insert(:user) + +      {:ok, follow_activity} = ActivityPub.follow(follower, user) +      {:ok, follow_activity_two} = ActivityPub.follow(follower, user) + +      data = +        follow_activity_two.data +        |> Map.put("state", "accept") + +      cng = Ecto.Changeset.change(follow_activity_two, data: data) + +      {:ok, follow_activity_two} = Repo.update(cng) + +      {: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" +    end +  end + +  describe "update_follow_state/2" do +    test "updates the state of the given follow activity" do +      user = insert(:user, info: %{locked: true}) +      follower = insert(:user) + +      {:ok, follow_activity} = ActivityPub.follow(follower, user) +      {:ok, follow_activity_two} = ActivityPub.follow(follower, user) + +      data = +        follow_activity_two.data +        |> Map.put("state", "accept") + +      cng = Ecto.Changeset.change(follow_activity_two, data: data) + +      {:ok, follow_activity_two} = Repo.update(cng) + +      {: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" +    end +  end  end diff --git a/test/web/activity_pub/views/object_view_test.exs b/test/web/activity_pub/views/object_view_test.exs index d939fc5a7..13447dc29 100644 --- a/test/web/activity_pub/views/object_view_test.exs +++ b/test/web/activity_pub/views/object_view_test.exs @@ -1,7 +1,12 @@ +# 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.ObjectViewTest do    use Pleroma.DataCase    import Pleroma.Factory +  alias Pleroma.Object    alias Pleroma.Web.ActivityPub.ObjectView    alias Pleroma.Web.CommonAPI @@ -19,19 +24,21 @@ defmodule Pleroma.Web.ActivityPub.ObjectViewTest do    test "renders a note activity" do      note = insert(:note_activity) +    object = Object.normalize(note)      result = ObjectView.render("object.json", %{object: note})      assert result["id"] == note.data["id"]      assert result["to"] == note.data["to"]      assert result["object"]["type"] == "Note" -    assert result["object"]["content"] == note.data["object"]["content"] +    assert result["object"]["content"] == object.data["content"]      assert result["type"] == "Create"      assert result["@context"]    end    test "renders a like activity" do      note = insert(:note_activity) +    object = Object.normalize(note)      user = insert(:user)      {:ok, like_activity, _} = CommonAPI.favorite(note.id, user) @@ -39,12 +46,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectViewTest do      result = ObjectView.render("object.json", %{object: like_activity})      assert result["id"] == like_activity.data["id"] -    assert result["object"] == note.data["object"]["id"] +    assert result["object"] == object.data["id"]      assert result["type"] == "Like"    end    test "renders an announce activity" do      note = insert(:note_activity) +    object = Object.normalize(note)      user = insert(:user)      {:ok, announce_activity, _} = CommonAPI.repeat(note.id, user) @@ -52,7 +60,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectViewTest do      result = ObjectView.render("object.json", %{object: announce_activity})      assert result["id"] == announce_activity.data["id"] -    assert result["object"] == note.data["object"]["id"] +    assert result["object"] == object.data["id"]      assert result["type"] == "Announce"    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 e6483db8b..fb7fd9e79 100644 --- a/test/web/activity_pub/views/user_view_test.exs +++ b/test/web/activity_pub/views/user_view_test.exs @@ -1,9 +1,14 @@ +# 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.UserViewTest do    use Pleroma.DataCase    import Pleroma.Factory    alias Pleroma.User    alias Pleroma.Web.ActivityPub.UserView +  alias Pleroma.Web.CommonAPI    test "Renders a user, including the public key" do      user = insert(:user) @@ -17,6 +22,21 @@ 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(%{info: %{fields: fields}}) +      |> User.update_and_set_cache() + +    assert %{ +             "attachment" => [%{"name" => "foo", "type" => "PropertyValue", "value" => "bar"}] +           } = 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) @@ -78,4 +98,28 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do        refute result["endpoints"]["oauthTokenEndpoint"]      end    end + +  describe "followers" do +    test "sets totalItems to zero when followers are hidden" 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}) +      info = Map.put(user.info, :hide_followers, true) +      user = Map.put(user, :info, info) +      assert %{"totalItems" => 0} = UserView.render("followers.json", %{user: user}) +    end +  end + +  describe "following" do +    test "sets totalItems to zero when follows are hidden" 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}) +      info = Map.put(user.info, :hide_follows, true) +      user = Map.put(user, :info, info) +      assert %{"totalItems" => 0} = UserView.render("following.json", %{user: user}) +    end +  end  end diff --git a/test/web/activity_pub/visibilty_test.exs b/test/web/activity_pub/visibilty_test.exs index e2584f635..b62a89e68 100644 --- a/test/web/activity_pub/visibilty_test.exs +++ b/test/web/activity_pub/visibilty_test.exs @@ -1,6 +1,11 @@ +# 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.VisibilityTest do    use Pleroma.DataCase +  alias Pleroma.Activity    alias Pleroma.Web.ActivityPub.Visibility    alias Pleroma.Web.CommonAPI    import Pleroma.Factory @@ -11,6 +16,9 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do      following = insert(:user)      unrelated = insert(:user)      {:ok, following} = Pleroma.User.follow(following, user) +    {:ok, list} = Pleroma.List.create("foo", user) + +    Pleroma.List.follow(list, unrelated)      {:ok, public} =        CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "public"}) @@ -24,6 +32,12 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do      {:ok, unlisted} =        CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "unlisted"}) +    {:ok, list} = +      CommonAPI.post(user, %{ +        "status" => "@#{mentioned.nickname}", +        "visibility" => "list:#{list.id}" +      }) +      %{        public: public,        private: private, @@ -32,29 +46,65 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do        user: user,        mentioned: mentioned,        following: following, -      unrelated: unrelated +      unrelated: unrelated, +      list: list      }    end -  test "is_direct?", %{public: public, private: private, direct: direct, unlisted: unlisted} do +  test "is_direct?", %{ +    public: public, +    private: private, +    direct: direct, +    unlisted: unlisted, +    list: list +  } do      assert Visibility.is_direct?(direct)      refute Visibility.is_direct?(public)      refute Visibility.is_direct?(private)      refute Visibility.is_direct?(unlisted) +    assert Visibility.is_direct?(list)    end -  test "is_public?", %{public: public, private: private, direct: direct, unlisted: unlisted} do +  test "is_public?", %{ +    public: public, +    private: private, +    direct: direct, +    unlisted: unlisted, +    list: list +  } do      refute Visibility.is_public?(direct)      assert Visibility.is_public?(public)      refute Visibility.is_public?(private)      assert Visibility.is_public?(unlisted) +    refute Visibility.is_public?(list)    end -  test "is_private?", %{public: public, private: private, direct: direct, unlisted: unlisted} do +  test "is_private?", %{ +    public: public, +    private: private, +    direct: direct, +    unlisted: unlisted, +    list: list +  } do      refute Visibility.is_private?(direct)      refute Visibility.is_private?(public)      assert Visibility.is_private?(private)      refute Visibility.is_private?(unlisted) +    refute Visibility.is_private?(list) +  end + +  test "is_list?", %{ +    public: public, +    private: private, +    direct: direct, +    unlisted: unlisted, +    list: list +  } do +    refute Visibility.is_list?(direct) +    refute Visibility.is_list?(public) +    refute Visibility.is_list?(private) +    refute Visibility.is_list?(unlisted) +    assert Visibility.is_list?(list)    end    test "visible_for_user?", %{ @@ -65,7 +115,8 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do      user: user,      mentioned: mentioned,      following: following, -    unrelated: unrelated +    unrelated: unrelated, +    list: list    } do      # All visible to author @@ -73,6 +124,7 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do      assert Visibility.visible_for_user?(private, user)      assert Visibility.visible_for_user?(unlisted, user)      assert Visibility.visible_for_user?(direct, user) +    assert Visibility.visible_for_user?(list, user)      # All visible to a mentioned user @@ -80,6 +132,7 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do      assert Visibility.visible_for_user?(private, mentioned)      assert Visibility.visible_for_user?(unlisted, mentioned)      assert Visibility.visible_for_user?(direct, mentioned) +    assert Visibility.visible_for_user?(list, mentioned)      # DM not visible for just follower @@ -87,6 +140,7 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do      assert Visibility.visible_for_user?(private, following)      assert Visibility.visible_for_user?(unlisted, following)      refute Visibility.visible_for_user?(direct, following) +    refute Visibility.visible_for_user?(list, following)      # Public and unlisted visible for unrelated user @@ -94,6 +148,9 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do      assert Visibility.visible_for_user?(unlisted, unrelated)      refute Visibility.visible_for_user?(private, unrelated)      refute Visibility.visible_for_user?(direct, unrelated) + +    # Visible for a list member +    assert Visibility.visible_for_user?(list, unrelated)    end    test "doesn't die when the user doesn't exist", @@ -110,11 +167,63 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do      public: public,      private: private,      direct: direct, -    unlisted: unlisted +    unlisted: unlisted, +    list: list    } do      assert Visibility.get_visibility(public) == "public"      assert Visibility.get_visibility(private) == "private"      assert Visibility.get_visibility(direct) == "direct"      assert Visibility.get_visibility(unlisted) == "unlisted" +    assert Visibility.get_visibility(list) == "list" +  end + +  test "get_visibility with directMessage flag" do +    assert Visibility.get_visibility(%{data: %{"directMessage" => true}}) == "direct" +  end + +  test "get_visibility with listMessage flag" do +    assert Visibility.get_visibility(%{data: %{"listMessage" => ""}}) == "list" +  end + +  describe "entire_thread_visible_for_user?/2" do +    test "returns false if not found activity", %{user: user} do +      refute Visibility.entire_thread_visible_for_user?(%Activity{}, user) +    end + +    test "returns true if activity hasn't 'Create' type", %{user: user} do +      activity = insert(:like_activity) +      assert Visibility.entire_thread_visible_for_user?(activity, user) +    end + +    test "returns false when invalid recipients", %{user: user} do +      author = insert(:user) + +      activity = +        insert(:note_activity, +          note: +            insert(:note, +              user: author, +              data: %{"to" => ["test-user"]} +            ) +        ) + +      refute Visibility.entire_thread_visible_for_user?(activity, user) +    end + +    test "returns true if user following to author" do +      author = insert(:user) +      user = insert(:user, following: [author.ap_id]) + +      activity = +        insert(:note_activity, +          note: +            insert(:note, +              user: author, +              data: %{"to" => [user.ap_id]} +            ) +        ) + +      assert Visibility.entire_thread_visible_for_user?(activity, user) +    end    end  end diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index 86b160246..ab829d6bd 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -6,9 +6,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do    use Pleroma.Web.ConnCase    alias Pleroma.Activity +  alias Pleroma.HTML    alias Pleroma.User    alias Pleroma.UserInviteToken    alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.MediaProxy    import Pleroma.Factory    describe "/api/pleroma/admin/users" do @@ -177,7 +179,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do          "local" => true,          "nickname" => user.nickname,          "roles" => %{"admin" => false, "moderator" => false}, -        "tags" => [] +        "tags" => [], +        "avatar" => User.avatar_url(user) |> MediaProxy.url(), +        "display_name" => HTML.strip_tags(user.name || user.nickname)        }        assert expected == json_response(conn, 200) @@ -409,18 +413,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do    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]) -      Pleroma.Config.put([:instance, :registrations_open], false) -      Pleroma.Config.put([:instance, :invites_enabled], true) +      [user: insert(:user, info: %{is_admin: true})] +    end -      on_exit(fn -> -        Pleroma.Config.put([:instance, :registrations_open], registrations_open) -        Pleroma.Config.put([:instance, :invites_enabled], invites_enabled) -        :ok -      end) +    clear_config([:instance, :registrations_open]) do +      Pleroma.Config.put([:instance, :registrations_open], false) +    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 @@ -475,18 +476,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest 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])        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) @@ -496,17 +492,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      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])        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) @@ -564,7 +552,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do              "nickname" => admin.nickname,              "roles" => %{"admin" => true, "moderator" => false},              "local" => true, -            "tags" => [] +            "tags" => [], +            "avatar" => User.avatar_url(admin) |> MediaProxy.url(), +            "display_name" => HTML.strip_tags(admin.name || admin.nickname)            },            %{              "deactivated" => user.info.deactivated, @@ -572,7 +562,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do              "nickname" => user.nickname,              "roles" => %{"admin" => false, "moderator" => false},              "local" => false, -            "tags" => ["foo", "bar"] +            "tags" => ["foo", "bar"], +            "avatar" => User.avatar_url(user) |> MediaProxy.url(), +            "display_name" => HTML.strip_tags(user.name || user.nickname)            }          ]          |> Enum.sort_by(& &1["nickname"]) @@ -611,7 +603,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                     "nickname" => user.nickname,                     "roles" => %{"admin" => false, "moderator" => false},                     "local" => true, -                   "tags" => [] +                   "tags" => [], +                   "avatar" => User.avatar_url(user) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(user.name || user.nickname)                   }                 ]               } @@ -633,7 +627,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                     "nickname" => user.nickname,                     "roles" => %{"admin" => false, "moderator" => false},                     "local" => true, -                   "tags" => [] +                   "tags" => [], +                   "avatar" => User.avatar_url(user) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(user.name || user.nickname)                   }                 ]               } @@ -655,7 +651,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                     "nickname" => user.nickname,                     "roles" => %{"admin" => false, "moderator" => false},                     "local" => true, -                   "tags" => [] +                   "tags" => [], +                   "avatar" => User.avatar_url(user) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(user.name || user.nickname)                   }                 ]               } @@ -677,7 +675,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                     "nickname" => user.nickname,                     "roles" => %{"admin" => false, "moderator" => false},                     "local" => true, -                   "tags" => [] +                   "tags" => [], +                   "avatar" => User.avatar_url(user) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(user.name || user.nickname)                   }                 ]               } @@ -699,7 +699,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                     "nickname" => user.nickname,                     "roles" => %{"admin" => false, "moderator" => false},                     "local" => true, -                   "tags" => [] +                   "tags" => [], +                   "avatar" => User.avatar_url(user) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(user.name || user.nickname)                   }                 ]               } @@ -721,7 +723,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                     "nickname" => user.nickname,                     "roles" => %{"admin" => false, "moderator" => false},                     "local" => true, -                   "tags" => [] +                   "tags" => [], +                   "avatar" => User.avatar_url(user) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(user.name || user.nickname)                   }                 ]               } @@ -738,7 +742,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                     "nickname" => user2.nickname,                     "roles" => %{"admin" => false, "moderator" => false},                     "local" => true, -                   "tags" => [] +                   "tags" => [], +                   "avatar" => User.avatar_url(user2) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(user2.name || user2.nickname)                   }                 ]               } @@ -765,7 +771,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                     "nickname" => user.nickname,                     "roles" => %{"admin" => false, "moderator" => false},                     "local" => true, -                   "tags" => [] +                   "tags" => [], +                   "avatar" => User.avatar_url(user) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(user.name || user.nickname)                   }                 ]               } @@ -790,7 +798,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do              "nickname" => user.nickname,              "roles" => %{"admin" => false, "moderator" => false},              "local" => true, -            "tags" => [] +            "tags" => [], +            "avatar" => User.avatar_url(user) |> MediaProxy.url(), +            "display_name" => HTML.strip_tags(user.name || user.nickname)            },            %{              "deactivated" => admin.info.deactivated, @@ -798,7 +808,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do              "nickname" => admin.nickname,              "roles" => %{"admin" => true, "moderator" => false},              "local" => true, -            "tags" => [] +            "tags" => [], +            "avatar" => User.avatar_url(admin) |> MediaProxy.url(), +            "display_name" => HTML.strip_tags(admin.name || admin.nickname)            },            %{              "deactivated" => false, @@ -806,7 +818,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do              "local" => true,              "nickname" => old_admin.nickname,              "roles" => %{"admin" => true, "moderator" => false}, -            "tags" => [] +            "tags" => [], +            "avatar" => User.avatar_url(old_admin) |> MediaProxy.url(), +            "display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname)            }          ]          |> Enum.sort_by(& &1["nickname"]) @@ -833,7 +847,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do              "nickname" => admin.nickname,              "roles" => %{"admin" => true, "moderator" => false},              "local" => admin.local, -            "tags" => [] +            "tags" => [], +            "avatar" => User.avatar_url(admin) |> MediaProxy.url(), +            "display_name" => HTML.strip_tags(admin.name || admin.nickname)            },            %{              "deactivated" => false, @@ -841,7 +857,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do              "nickname" => second_admin.nickname,              "roles" => %{"admin" => true, "moderator" => false},              "local" => second_admin.local, -            "tags" => [] +            "tags" => [], +            "avatar" => User.avatar_url(second_admin) |> MediaProxy.url(), +            "display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname)            }          ]          |> Enum.sort_by(& &1["nickname"]) @@ -870,7 +888,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                     "nickname" => moderator.nickname,                     "roles" => %{"admin" => false, "moderator" => true},                     "local" => moderator.local, -                   "tags" => [] +                   "tags" => [], +                   "avatar" => User.avatar_url(moderator) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(moderator.name || moderator.nickname)                   }                 ]               } @@ -892,7 +912,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do              "nickname" => user1.nickname,              "roles" => %{"admin" => false, "moderator" => false},              "local" => user1.local, -            "tags" => ["first"] +            "tags" => ["first"], +            "avatar" => User.avatar_url(user1) |> MediaProxy.url(), +            "display_name" => HTML.strip_tags(user1.name || user1.nickname)            },            %{              "deactivated" => false, @@ -900,7 +922,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do              "nickname" => user2.nickname,              "roles" => %{"admin" => false, "moderator" => false},              "local" => user2.local, -            "tags" => ["second"] +            "tags" => ["second"], +            "avatar" => User.avatar_url(user2) |> MediaProxy.url(), +            "display_name" => HTML.strip_tags(user2.name || user2.nickname)            }          ]          |> Enum.sort_by(& &1["nickname"]) @@ -934,7 +958,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                     "nickname" => user.nickname,                     "roles" => %{"admin" => false, "moderator" => false},                     "local" => user.local, -                   "tags" => [] +                   "tags" => [], +                   "avatar" => User.avatar_url(user) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(user.name || user.nickname)                   }                 ]               } @@ -957,7 +983,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                 "nickname" => user.nickname,                 "roles" => %{"admin" => false, "moderator" => false},                 "local" => true, -               "tags" => [] +               "tags" => [], +               "avatar" => User.avatar_url(user) |> MediaProxy.url(), +               "display_name" => HTML.strip_tags(user.name || user.nickname)               }    end @@ -1085,6 +1113,17 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                 "uses" => 0               }      end + +    test "with invalid token" do +      admin = insert(:user, info: %{is_admin: true}) + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> post("/api/pleroma/admin/users/revoke_invite", %{"token" => "foo"}) + +      assert json_response(conn, :not_found) == "Not found" +    end    end    describe "GET /api/pleroma/admin/reports/:id" do @@ -1309,7 +1348,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        recipients = Enum.map(response["mentions"], & &1["username"]) -      assert conn.assigns[:user].nickname in recipients        assert reporter.nickname in recipients        assert response["content"] == "I will check it out"        assert response["visibility"] == "direct" @@ -1411,4 +1449,701 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        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}) + +      %{conn: assign(conn, :user, admin)} +    end + +    test "without any settings in db", %{conn: conn} do +      conn = get(conn, "/api/pleroma/admin/config") + +      assert json_response(conn, 200) == %{"configs" => []} +    end + +    test "with settings in db", %{conn: conn} do +      config1 = insert(:config) +      config2 = insert(:config) + +      conn = get(conn, "/api/pleroma/admin/config") + +      %{ +        "configs" => [ +          %{ +            "key" => key1, +            "value" => _ +          }, +          %{ +            "key" => key2, +            "value" => _ +          } +        ] +      } = json_response(conn, 200) + +      assert key1 == config1.key +      assert key2 == config2.key +    end +  end + +  describe "POST /api/pleroma/admin/config" do +    setup %{conn: conn} do +      admin = insert(:user, info: %{is_admin: true}) + +      temp_file = "config/test.exported_from_db.secret.exs" + +      on_exit(fn -> +        Application.delete_env(:pleroma, :key1) +        Application.delete_env(:pleroma, :key2) +        Application.delete_env(:pleroma, :key3) +        Application.delete_env(:pleroma, :key4) +        Application.delete_env(:pleroma, :keyaa1) +        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) + +      %{conn: assign(conn, :user, admin)} +    end + +    clear_config([:instance, :dynamic_configuration]) do +      Pleroma.Config.put([:instance, :dynamic_configuration], true) +    end + +    test "create new config setting in db", %{conn: conn} do +      conn = +        post(conn, "/api/pleroma/admin/config", %{ +          configs: [ +            %{group: "pleroma", key: "key1", value: "value1"}, +            %{ +              group: "ueberauth", +              key: "Ueberauth.Strategy.Twitter.OAuth", +              value: [%{"tuple" => [":consumer_secret", "aaaa"]}] +            }, +            %{ +              group: "pleroma", +              key: "key2", +              value: %{ +                ":nested_1" => "nested_value1", +                ":nested_2" => [ +                  %{":nested_22" => "nested_value222"}, +                  %{":nested_33" => %{":nested_44" => "nested_444"}} +                ] +              } +            }, +            %{ +              group: "pleroma", +              key: "key3", +              value: [ +                %{"nested_3" => ":nested_3", "nested_33" => "nested_33"}, +                %{"nested_4" => true} +              ] +            }, +            %{ +              group: "pleroma", +              key: "key4", +              value: %{":nested_5" => ":upload", "endpoint" => "https://example.com"} +            }, +            %{ +              group: "idna", +              key: "key5", +              value: %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]} +            } +          ] +        }) + +      assert json_response(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => "pleroma", +                   "key" => "key1", +                   "value" => "value1" +                 }, +                 %{ +                   "group" => "ueberauth", +                   "key" => "Ueberauth.Strategy.Twitter.OAuth", +                   "value" => [%{"tuple" => [":consumer_secret", "aaaa"]}] +                 }, +                 %{ +                   "group" => "pleroma", +                   "key" => "key2", +                   "value" => %{ +                     ":nested_1" => "nested_value1", +                     ":nested_2" => [ +                       %{":nested_22" => "nested_value222"}, +                       %{":nested_33" => %{":nested_44" => "nested_444"}} +                     ] +                   } +                 }, +                 %{ +                   "group" => "pleroma", +                   "key" => "key3", +                   "value" => [ +                     %{"nested_3" => ":nested_3", "nested_33" => "nested_33"}, +                     %{"nested_4" => true} +                   ] +                 }, +                 %{ +                   "group" => "pleroma", +                   "key" => "key4", +                   "value" => %{"endpoint" => "https://example.com", ":nested_5" => ":upload"} +                 }, +                 %{ +                   "group" => "idna", +                   "key" => "key5", +                   "value" => %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]} +                 } +               ] +             } + +      assert Application.get_env(:pleroma, :key1) == "value1" + +      assert Application.get_env(:pleroma, :key2) == %{ +               nested_1: "nested_value1", +               nested_2: [ +                 %{nested_22: "nested_value222"}, +                 %{nested_33: %{nested_44: "nested_444"}} +               ] +             } + +      assert Application.get_env(:pleroma, :key3) == [ +               %{"nested_3" => :nested_3, "nested_33" => "nested_33"}, +               %{"nested_4" => true} +             ] + +      assert Application.get_env(:pleroma, :key4) == %{ +               "endpoint" => "https://example.com", +               nested_5: :upload +             } + +      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") + +      insert(:config, +        group: "ueberauth", +        key: "Ueberauth.Strategy.Microsoft.OAuth", +        value: :erlang.term_to_binary([]) +      ) + +      conn = +        post(conn, "/api/pleroma/admin/config", %{ +          configs: [ +            %{group: config1.group, key: config1.key, value: "another_value"}, +            %{group: config2.group, key: config2.key, delete: "true"}, +            %{ +              group: "ueberauth", +              key: "Ueberauth.Strategy.Microsoft.OAuth", +              delete: "true" +            } +          ] +        }) + +      assert json_response(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => "pleroma", +                   "key" => config1.key, +                   "value" => "another_value" +                 } +               ] +             } + +      assert Application.get_env(:pleroma, :keyaa1) == "another_value" +      refute Application.get_env(:pleroma, :keyaa2) +    end + +    test "common config example", %{conn: conn} do +      conn = +        post(conn, "/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              "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" => [":partial_chain", "&:hackney_connect.partial_chain/1"]} +              ] +            } +          ] +        }) + +      assert json_response(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "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" => [":partial_chain", "&:hackney_connect.partial_chain/1"]} +                   ] +                 } +               ] +             } +    end + +    test "tuples with more than two values", %{conn: conn} do +      conn = +        post(conn, "/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              "group" => "pleroma", +              "key" => "Pleroma.Web.Endpoint.NotReal", +              "value" => [ +                %{ +                  "tuple" => [ +                    ":http", +                    [ +                      %{ +                        "tuple" => [ +                          ":key2", +                          [ +                            %{ +                              "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 json_response(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => "pleroma", +                   "key" => "Pleroma.Web.Endpoint.NotReal", +                   "value" => [ +                     %{ +                       "tuple" => [ +                         ":http", +                         [ +                           %{ +                             "tuple" => [ +                               ":key2", +                               [ +                                 %{ +                                   "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", []]} +                                         ] +                                       } +                                     ] +                                   ] +                                 } +                               ] +                             ] +                           } +                         ] +                       ] +                     } +                   ] +                 } +               ] +             } +    end + +    test "settings with nesting map", %{conn: conn} do +      conn = +        post(conn, "/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              "group" => "pleroma", +              "key" => ":key1", +              "value" => [ +                %{"tuple" => [":key2", "some_val"]}, +                %{ +                  "tuple" => [ +                    ":key3", +                    %{ +                      ":max_options" => 20, +                      ":max_option_chars" => 200, +                      ":min_expiration" => 0, +                      ":max_expiration" => 31_536_000, +                      "nested" => %{ +                        ":max_options" => 20, +                        ":max_option_chars" => 200, +                        ":min_expiration" => 0, +                        ":max_expiration" => 31_536_000 +                      } +                    } +                  ] +                } +              ] +            } +          ] +        }) + +      assert json_response(conn, 200) == +               %{ +                 "configs" => [ +                   %{ +                     "group" => "pleroma", +                     "key" => ":key1", +                     "value" => [ +                       %{"tuple" => [":key2", "some_val"]}, +                       %{ +                         "tuple" => [ +                           ":key3", +                           %{ +                             ":max_expiration" => 31_536_000, +                             ":max_option_chars" => 200, +                             ":max_options" => 20, +                             ":min_expiration" => 0, +                             "nested" => %{ +                               ":max_expiration" => 31_536_000, +                               ":max_option_chars" => 200, +                               ":max_options" => 20, +                               ":min_expiration" => 0 +                             } +                           } +                         ] +                       } +                     ] +                   } +                 ] +               } +    end + +    test "value as map", %{conn: conn} do +      conn = +        post(conn, "/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              "group" => "pleroma", +              "key" => ":key1", +              "value" => %{"key" => "some_val"} +            } +          ] +        }) + +      assert json_response(conn, 200) == +               %{ +                 "configs" => [ +                   %{ +                     "group" => "pleroma", +                     "key" => ":key1", +                     "value" => %{"key" => "some_val"} +                   } +                 ] +               } +    end + +    test "dispatch setting", %{conn: conn} do +      conn = +        post(conn, "/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              "group" => "pleroma", +              "key" => "Pleroma.Web.Endpoint.NotReal", +              "value" => [ +                %{ +                  "tuple" => [ +                    ":http", +                    [ +                      %{"tuple" => [":ip", %{"tuple" => [127, 0, 0, 1]}]}, +                      %{"tuple" => [":dispatch", ["{:_, +       [ +         {\"/api/v1/streaming\", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, +         {\"/websocket\", Phoenix.Endpoint.CowboyWebSocket, +          {Phoenix.Transports.WebSocket, +           {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: \"/websocket\"]}}}, +         {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}} +       ]}"]]} +                    ] +                  ] +                } +              ] +            } +          ] +        }) + +      dispatch_string = +        "{:_, [{\"/api/v1/streaming\", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, " <> +          "{\"/websocket\", Phoenix.Endpoint.CowboyWebSocket, {Phoenix.Transports.WebSocket, " <> +          "{Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: \"/websocket\"]}}}, " <> +          "{:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}]}" + +      assert json_response(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => "pleroma", +                   "key" => "Pleroma.Web.Endpoint.NotReal", +                   "value" => [ +                     %{ +                       "tuple" => [ +                         ":http", +                         [ +                           %{"tuple" => [":ip", %{"tuple" => [127, 0, 0, 1]}]}, +                           %{ +                             "tuple" => [ +                               ":dispatch", +                               [ +                                 dispatch_string +                               ] +                             ] +                           } +                         ] +                       ] +                     } +                   ] +                 } +               ] +             } +    end + +    test "queues key as atom", %{conn: conn} do +      conn = +        post(conn, "/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              "group" => "pleroma_job_queue", +              "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]} +              ] +            } +          ] +        }) + +      assert json_response(conn, 200) == %{ +               "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]} +                   ] +                 } +               ] +             } +    end + +    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: config.group, +              key: config.key, +              subkeys: [":subkey1", ":subkey3"], +              delete: "true" +            } +          ] +        }) + +      assert( +        json_response(conn, 200) == %{ +          "configs" => [ +            %{ +              "group" => "pleroma", +              "key" => "keyaa1", +              "value" => [%{"tuple" => [":subkey2", "val2"]}] +            } +          ] +        } +      ) +    end +  end + +  describe "config mix tasks run" do +    setup %{conn: conn} do +      admin = insert(:user, info: %{is_admin: true}) + +      temp_file = "config/test.exported_from_db.secret.exs" + +      Mix.shell(Mix.Shell.Quiet) + +      on_exit(fn -> +        Mix.shell(Mix.Shell.IO) +        :ok = File.rm(temp_file) +      end) + +      %{conn: assign(conn, :user, admin), admin: admin} +    end + +    clear_config([:instance, :dynamic_configuration]) do +      Pleroma.Config.put([:instance, :dynamic_configuration], true) +    end + +    test "transfer settings to DB and to file", %{conn: conn, admin: admin} do +      assert Pleroma.Repo.all(Pleroma.Web.AdminAPI.Config) == [] +      conn = get(conn, "/api/pleroma/admin/config/migrate_to_db") +      assert json_response(conn, 200) == %{} +      assert Pleroma.Repo.all(Pleroma.Web.AdminAPI.Config) > 0 + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> get("/api/pleroma/admin/config/migrate_from_db") + +      assert json_response(conn, 200) == %{} +      assert Pleroma.Repo.all(Pleroma.Web.AdminAPI.Config) == [] +    end +  end + +  describe "GET /api/pleroma/admin/users/:nickname/statuses" do +    setup do +      admin = insert(:user, info: %{is_admin: true}) +      user = insert(:user) + +      date1 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!() +      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) + +      conn = +        build_conn() +        |> assign(:user, admin) + +      {:ok, conn: conn, 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 +end + +# Needed for testing +defmodule Pleroma.Web.Endpoint.NotReal do +end + +defmodule Pleroma.Captcha.NotReal do  end diff --git a/test/web/admin_api/config_test.exs b/test/web/admin_api/config_test.exs new file mode 100644 index 000000000..3190dc1c8 --- /dev/null +++ b/test/web/admin_api/config_test.exs @@ -0,0 +1,473 @@ +# 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 with partial_chain key" do +      binary = +        Config.transform([%{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}]) + +      assert binary == :erlang.term_to_binary(partial_chain: &:hackney_connect.partial_chain/1) +      assert Config.from_binary(binary) == [partial_chain: &:hackney_connect.partial_chain/1] +    end + +    test "keyword" do +      binary = +        Config.transform([ +          %{"tuple" => [":types", "Pleroma.PostgresTypes"]}, +          %{"tuple" => [":telemetry_event", ["Pleroma.Repo.Instrumenter"]]}, +          %{"tuple" => [":migration_lock", nil]}, +          %{"tuple" => [":key1", 150]}, +          %{"tuple" => [":key2", "string"]} +        ]) + +      assert binary == +               :erlang.term_to_binary( +                 types: Pleroma.PostgresTypes, +                 telemetry_event: [Pleroma.Repo.Instrumenter], +                 migration_lock: nil, +                 key1: 150, +                 key2: "string" +               ) + +      assert Config.from_binary(binary) == [ +               types: Pleroma.PostgresTypes, +               telemetry_event: [Pleroma.Repo.Instrumenter], +               migration_lock: nil, +               key1: 150, +               key2: "string" +             ] +    end + +    test "complex keyword with nested mixed childs" do +      binary = +        Config.transform([ +          %{"tuple" => [":uploader", "Pleroma.Uploaders.Local"]}, +          %{"tuple" => [":filters", ["Pleroma.Upload.Filter.Dedupe"]]}, +          %{"tuple" => [":link_name", true]}, +          %{"tuple" => [":proxy_remote", false]}, +          %{"tuple" => [":common_map", %{":key" => "value"}]}, +          %{ +            "tuple" => [ +              ":proxy_opts", +              [ +                %{"tuple" => [":redirect_on_failure", false]}, +                %{"tuple" => [":max_body_length", 1_048_576]}, +                %{ +                  "tuple" => [ +                    ":http", +                    [%{"tuple" => [":follow_redirect", true]}, %{"tuple" => [":pool", ":upload"]}] +                  ] +                } +              ] +            ] +          } +        ]) + +      assert binary == +               :erlang.term_to_binary( +                 uploader: Pleroma.Uploaders.Local, +                 filters: [Pleroma.Upload.Filter.Dedupe], +                 link_name: true, +                 proxy_remote: false, +                 common_map: %{key: "value"}, +                 proxy_opts: [ +                   redirect_on_failure: false, +                   max_body_length: 1_048_576, +                   http: [ +                     follow_redirect: true, +                     pool: :upload +                   ] +                 ] +               ) + +      assert Config.from_binary(binary) == +               [ +                 uploader: Pleroma.Uploaders.Local, +                 filters: [Pleroma.Upload.Filter.Dedupe], +                 link_name: true, +                 proxy_remote: false, +                 common_map: %{key: "value"}, +                 proxy_opts: [ +                   redirect_on_failure: false, +                   max_body_length: 1_048_576, +                   http: [ +                     follow_redirect: true, +                     pool: :upload +                   ] +                 ] +               ] +    end + +    test "common keyword" do +      binary = +        Config.transform([ +          %{"tuple" => [":level", ":warn"]}, +          %{"tuple" => [":meta", [":all"]]}, +          %{"tuple" => [":path", ""]}, +          %{"tuple" => [":val", nil]}, +          %{"tuple" => [":webhook_url", "https://hooks.slack.com/services/YOUR-KEY-HERE"]} +        ]) + +      assert binary == +               :erlang.term_to_binary( +                 level: :warn, +                 meta: [:all], +                 path: "", +                 val: nil, +                 webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE" +               ) + +      assert Config.from_binary(binary) == [ +               level: :warn, +               meta: [:all], +               path: "", +               val: nil, +               webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE" +             ] +    end + +    test "complex keyword with sigil" do +      binary = +        Config.transform([ +          %{"tuple" => [":federated_timeline_removal", []]}, +          %{"tuple" => [":reject", ["~r/comp[lL][aA][iI][nN]er/"]]}, +          %{"tuple" => [":replace", []]} +        ]) + +      assert binary == +               :erlang.term_to_binary( +                 federated_timeline_removal: [], +                 reject: [~r/comp[lL][aA][iI][nN]er/], +                 replace: [] +               ) + +      assert Config.from_binary(binary) == +               [federated_timeline_removal: [], reject: [~r/comp[lL][aA][iI][nN]er/], replace: []] +    end + +    test "complex keyword with tuples with more than 2 values" do +      binary = +        Config.transform([ +          %{ +            "tuple" => [ +              ":http", +              [ +                %{ +                  "tuple" => [ +                    ":key1", +                    [ +                      %{ +                        "tuple" => [ +                          ":_", +                          [ +                            %{ +                              "tuple" => [ +                                "/api/v1/streaming", +                                "Pleroma.Web.MastodonAPI.WebsocketHandler", +                                [] +                              ] +                            }, +                            %{ +                              "tuple" => [ +                                "/websocket", +                                "Phoenix.Endpoint.CowboyWebSocket", +                                %{ +                                  "tuple" => [ +                                    "Phoenix.Transports.WebSocket", +                                    %{ +                                      "tuple" => [ +                                        "Pleroma.Web.Endpoint", +                                        "Pleroma.Web.UserSocket", +                                        [] +                                      ] +                                    } +                                  ] +                                } +                              ] +                            }, +                            %{ +                              "tuple" => [ +                                ":_", +                                "Phoenix.Endpoint.Cowboy2Handler", +                                %{"tuple" => ["Pleroma.Web.Endpoint", []]} +                              ] +                            } +                          ] +                        ] +                      } +                    ] +                  ] +                } +              ] +            ] +          } +        ]) + +      assert binary == +               :erlang.term_to_binary( +                 http: [ +                   key1: [ +                     _: [ +                       {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, +                       {"/websocket", Phoenix.Endpoint.CowboyWebSocket, +                        {Phoenix.Transports.WebSocket, +                         {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, []}}}, +                       {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}} +                     ] +                   ] +                 ] +               ) + +      assert Config.from_binary(binary) == [ +               http: [ +                 key1: [ +                   {:_, +                    [ +                      {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, +                      {"/websocket", Phoenix.Endpoint.CowboyWebSocket, +                       {Phoenix.Transports.WebSocket, +                        {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, []}}}, +                      {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}} +                    ]} +                 ] +               ] +             ] +    end +  end +end diff --git a/test/web/admin_api/views/report_view_test.exs b/test/web/admin_api/views/report_view_test.exs new file mode 100644 index 000000000..a00c9c579 --- /dev/null +++ b/test/web/admin_api/views/report_view_test.exs @@ -0,0 +1,130 @@ +# 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.ReportViewTest do +  use Pleroma.DataCase +  import Pleroma.Factory +  alias Pleroma.Web.AdminAPI.ReportView +  alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.MastodonAPI.AccountView +  alias Pleroma.Web.MastodonAPI.StatusView + +  test "renders a report" do +    user = insert(:user) +    other_user = insert(:user) + +    {:ok, activity} = CommonAPI.report(user, %{"account_id" => other_user.id}) + +    expected = %{ +      content: nil, +      actor: +        Map.merge( +          AccountView.render("account.json", %{user: user}), +          Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user}) +        ), +      account: +        Map.merge( +          AccountView.render("account.json", %{user: other_user}), +          Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: other_user}) +        ), +      statuses: [], +      state: "open", +      id: activity.id +    } + +    result = +      ReportView.render("show.json", %{report: activity}) +      |> Map.delete(:created_at) + +    assert result == expected +  end + +  test "includes reported statuses" do +    user = insert(:user) +    other_user = insert(:user) +    {:ok, activity} = CommonAPI.post(other_user, %{"status" => "toot"}) + +    {:ok, report_activity} = +      CommonAPI.report(user, %{"account_id" => other_user.id, "status_ids" => [activity.id]}) + +    expected = %{ +      content: nil, +      actor: +        Map.merge( +          AccountView.render("account.json", %{user: user}), +          Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user}) +        ), +      account: +        Map.merge( +          AccountView.render("account.json", %{user: other_user}), +          Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: other_user}) +        ), +      statuses: [StatusView.render("status.json", %{activity: activity})], +      state: "open", +      id: report_activity.id +    } + +    result = +      ReportView.render("show.json", %{report: report_activity}) +      |> Map.delete(:created_at) + +    assert result == expected +  end + +  test "renders report's state" do +    user = insert(:user) +    other_user = insert(:user) + +    {: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}) +  end + +  test "renders report description" do +    user = insert(:user) +    other_user = insert(:user) + +    {:ok, activity} = +      CommonAPI.report(user, %{ +        "account_id" => other_user.id, +        "comment" => "posts are too good for this instance" +      }) + +    assert %{content: "posts are too good for this instance"} = +             ReportView.render("show.json", %{report: activity}) +  end + +  test "sanitizes report description" do +    user = insert(:user) +    other_user = insert(:user) + +    {:ok, activity} = +      CommonAPI.report(user, %{ +        "account_id" => other_user.id, +        "comment" => "" +      }) + +    data = Map.put(activity.data, "content", "<script> alert('hecked :D:D:D:D:D:D:D') </script>") +    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] +  end + +  test "doesn't error out when the user doesn't exists" do +    user = insert(:user) +    other_user = insert(:user) + +    {:ok, activity} = +      CommonAPI.report(user, %{ +        "account_id" => other_user.id, +        "comment" => "" +      }) + +    Pleroma.User.delete(other_user) +    Pleroma.User.invalidate_cache(other_user) + +    assert %{} = ReportView.render("show.json", %{report: activity}) +  end +end diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 696060fb1..f28a66090 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -5,17 +5,66 @@  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.CommonAPI    import Pleroma.Factory +  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} = @@ -26,14 +75,13 @@ 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      user = insert(:user)      {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu #2HU"}) -    object = Object.normalize(activity.data["object"]) +    object = Object.normalize(activity)      assert object.data["tag"] == ["2hu"]    end @@ -56,6 +104,25 @@ defmodule Pleroma.Web.CommonAPITest do    end    describe "posting" do +    test "it supports explicit addressing" do +      user = insert(:user) +      user_two = insert(:user) +      user_three = insert(:user) +      user_four = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => +            "Hey, I think @#{user_three.nickname} is ugly. @#{user_four.nickname} is alright though.", +          "to" => [user_two.nickname, user_four.nickname, "nonexistent"] +        }) + +      assert user.ap_id in activity.recipients +      assert user_two.ap_id in activity.recipients +      assert user_four.ap_id in activity.recipients +      refute user_three.ap_id in activity.recipients +    end +      test "it filters out obviously bad tags when accepting a post as HTML" do        user = insert(:user) @@ -67,7 +134,7 @@ defmodule Pleroma.Web.CommonAPITest do            "content_type" => "text/html"          }) -      object = Object.normalize(activity.data["object"]) +      object = Object.normalize(activity)        assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"      end @@ -83,7 +150,7 @@ defmodule Pleroma.Web.CommonAPITest do            "content_type" => "text/markdown"          }) -      object = Object.normalize(activity.data["object"]) +      object = Object.normalize(activity)        assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"      end @@ -101,7 +168,7 @@ defmodule Pleroma.Web.CommonAPITest do                 })        Enum.each(["public", "private", "unlisted"], fn visibility -> -        assert {:error, {:private_to_public, _}} = +        assert {:error, "The message visibility must be direct"} =                   CommonAPI.post(user, %{                     "status" => "suya..",                     "visibility" => visibility, @@ -109,6 +176,49 @@ defmodule Pleroma.Web.CommonAPITest do                   })        end)      end + +    test "it allows to address a list" do +      user = insert(:user) +      {:ok, list} = Pleroma.List.create("foo", user) + +      {:ok, activity} = +        CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"}) + +      assert activity.data["bcc"] == [list.ap_id] +      assert activity.recipients == [list.ap_id, user.ap_id] +      assert activity.data["listMessage"] == list.ap_id +    end + +    test "it returns error when status is empty and no attachments" do +      user = insert(:user) + +      assert {:error, "Cannot post an empty status without attachments"} = +               CommonAPI.post(user, %{"status" => ""}) +    end + +    test "it returns error when character limit is exceeded" do +      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) + +      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 @@ -168,6 +278,11 @@ defmodule Pleroma.Web.CommonAPITest do        assert %User{info: %{pinned_activities: [^id]}} = user      end +    test "unlisted statuses can be pinned", %{user: user} do +      {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!", "visibility" => "unlisted"}) +      assert {:ok, ^activity} = CommonAPI.pin(activity.id, user) +    end +      test "only self-authored can be pinned", %{activity: activity} do        user = insert(:user) @@ -320,4 +435,79 @@ defmodule Pleroma.Web.CommonAPITest do        assert User.showing_reblogs?(muter, muted) == true      end    end + +  describe "unfollow/2" 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) + +      assert User.subscribed_to?(follower, followed) + +      {:ok, follower} = CommonAPI.unfollow(follower, followed) + +      refute User.subscribed_to?(follower, followed) +    end +  end + +  describe "accept_follow_request/2" do +    test "after acceptance, it sets all existing pending follow request states to 'accept'" do +      user = insert(:user, info: %{locked: true}) +      follower = insert(:user) +      follower_two = insert(:user) + +      {:ok, follow_activity} = ActivityPub.follow(follower, user) +      {:ok, follow_activity_two} = ActivityPub.follow(follower, user) +      {:ok, follow_activity_three} = ActivityPub.follow(follower_two, user) + +      assert follow_activity.data["state"] == "pending" +      assert follow_activity_two.data["state"] == "pending" +      assert follow_activity_three.data["state"] == "pending" + +      {:ok, _follower} = CommonAPI.accept_follow_request(follower, user) + +      assert Repo.get(Activity, follow_activity.id).data["state"] == "accept" +      assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept" +      assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending" +    end + +    test "after rejection, it sets all existing pending follow request states to 'reject'" do +      user = insert(:user, info: %{locked: true}) +      follower = insert(:user) +      follower_two = insert(:user) + +      {:ok, follow_activity} = ActivityPub.follow(follower, user) +      {:ok, follow_activity_two} = ActivityPub.follow(follower, user) +      {:ok, follow_activity_three} = ActivityPub.follow(follower_two, user) + +      assert follow_activity.data["state"] == "pending" +      assert follow_activity_two.data["state"] == "pending" +      assert follow_activity_three.data["state"] == "pending" + +      {:ok, _follower} = CommonAPI.reject_follow_request(follower, user) + +      assert Repo.get(Activity, follow_activity.id).data["state"] == "reject" +      assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject" +      assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending" +    end +  end + +  describe "vote/3" do +    test "does not allow to vote twice" 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) + +      {:ok, _, object} = CommonAPI.vote(other_user, object, [0]) + +      assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1]) +    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 ab4c62b35..c281dd1f1 100644 --- a/test/web/common_api/common_api_utils_test.exs +++ b/test/web/common_api/common_api_utils_test.exs @@ -5,10 +5,16 @@  defmodule Pleroma.Web.CommonAPI.UtilsTest do    alias Pleroma.Builders.UserBuilder    alias Pleroma.Object +  alias Pleroma.Web.CommonAPI    alias Pleroma.Web.CommonAPI.Utils    alias Pleroma.Web.Endpoint    use Pleroma.DataCase +  import ExUnit.CaptureLog +  import Pleroma.Factory + +  @public_address "https://www.w3.org/ns/activitystreams#Public" +    test "it adds attachment links to a given text and attachment set" do      name =        "Sakura%20Mana%20%E2%80%93%20Turned%20on%20by%20a%20Senior%20OL%20with%20a%20Temptating%20Tight%20Skirt-s%20Full%20Hipline%20and%20Panty%20Shot-%20Beautiful%20Thick%20Thighs-%20and%20Erotic%20Ass-%20-2015-%20--%20Oppaitime%208-28-2017%206-50-33%20PM.png" @@ -197,7 +203,9 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do        expected = "" -      assert Utils.date_to_asctime(date) == expected +      assert capture_log(fn -> +               assert Utils.date_to_asctime(date) == expected +             end) =~ "[warn] Date #{date} in wrong format, must be ISO 8601"      end      test "when date is a Unix timestamp" do @@ -205,13 +213,388 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do        expected = "" -      assert Utils.date_to_asctime(date) == expected +      assert capture_log(fn -> +               assert Utils.date_to_asctime(date) == expected +             end) =~ "[warn] Date #{date} in wrong format, must be ISO 8601"      end      test "when date is nil" do        expected = "" -      assert Utils.date_to_asctime(nil) == expected +      assert capture_log(fn -> +               assert Utils.date_to_asctime(nil) == expected +             end) =~ "[warn] Date  in wrong format, must be ISO 8601" +    end + +    test "when date is a random string" do +      assert capture_log(fn -> +               assert Utils.date_to_asctime("foo") == "" +             end) =~ "[warn] Date foo in wrong format, must be ISO 8601" +    end +  end + +  describe "get_to_and_cc" do +    test "for public posts, not a reply" do +      user = insert(:user) +      mentioned_user = insert(:user) +      mentions = [mentioned_user.ap_id] + +      {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "public", nil) + +      assert length(to) == 2 +      assert length(cc) == 1 + +      assert @public_address in to +      assert mentioned_user.ap_id in to +      assert user.follower_address in cc +    end + +    test "for public posts, a reply" do +      user = insert(:user) +      mentioned_user = insert(:user) +      third_user = insert(:user) +      {:ok, activity} = CommonAPI.post(third_user, %{"status" => "uguu"}) +      mentions = [mentioned_user.ap_id] + +      {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "public", nil) + +      assert length(to) == 3 +      assert length(cc) == 1 + +      assert @public_address in to +      assert mentioned_user.ap_id in to +      assert third_user.ap_id in to +      assert user.follower_address in cc +    end + +    test "for unlisted posts, not a reply" do +      user = insert(:user) +      mentioned_user = insert(:user) +      mentions = [mentioned_user.ap_id] + +      {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "unlisted", nil) + +      assert length(to) == 2 +      assert length(cc) == 1 + +      assert @public_address in cc +      assert mentioned_user.ap_id in to +      assert user.follower_address in to +    end + +    test "for unlisted posts, a reply" do +      user = insert(:user) +      mentioned_user = insert(:user) +      third_user = insert(:user) +      {:ok, activity} = CommonAPI.post(third_user, %{"status" => "uguu"}) +      mentions = [mentioned_user.ap_id] + +      {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "unlisted", nil) + +      assert length(to) == 3 +      assert length(cc) == 1 + +      assert @public_address in cc +      assert mentioned_user.ap_id in to +      assert third_user.ap_id in to +      assert user.follower_address in to +    end + +    test "for private posts, not a reply" do +      user = insert(:user) +      mentioned_user = insert(:user) +      mentions = [mentioned_user.ap_id] + +      {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "private", nil) +      assert length(to) == 2 +      assert length(cc) == 0 + +      assert mentioned_user.ap_id in to +      assert user.follower_address in to +    end + +    test "for private posts, a reply" do +      user = insert(:user) +      mentioned_user = insert(:user) +      third_user = insert(:user) +      {:ok, activity} = CommonAPI.post(third_user, %{"status" => "uguu"}) +      mentions = [mentioned_user.ap_id] + +      {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "private", nil) + +      assert length(to) == 3 +      assert length(cc) == 0 + +      assert mentioned_user.ap_id in to +      assert third_user.ap_id in to +      assert user.follower_address in to +    end + +    test "for direct posts, not a reply" do +      user = insert(:user) +      mentioned_user = insert(:user) +      mentions = [mentioned_user.ap_id] + +      {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "direct", nil) + +      assert length(to) == 1 +      assert length(cc) == 0 + +      assert mentioned_user.ap_id in to +    end + +    test "for direct posts, a reply" do +      user = insert(:user) +      mentioned_user = insert(:user) +      third_user = insert(:user) +      {:ok, activity} = CommonAPI.post(third_user, %{"status" => "uguu"}) +      mentions = [mentioned_user.ap_id] + +      {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "direct", nil) + +      assert length(to) == 2 +      assert length(cc) == 0 + +      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/digest_email_worker_test.exs b/test/web/digest_email_worker_test.exs new file mode 100644 index 000000000..15002330f --- /dev/null +++ b/test/web/digest_email_worker_test.exs @@ -0,0 +1,31 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.DigestEmailWorkerTest do +  use Pleroma.DataCase +  import Pleroma.Factory + +  alias Pleroma.DigestEmailWorker +  alias Pleroma.User +  alias Pleroma.Web.CommonAPI + +  test "it sends digest emails" do +    user = insert(:user) + +    date = +      Timex.now() +      |> Timex.shift(days: -10) +      |> Timex.to_naive_datetime() + +    user2 = insert(:user, last_digest_emailed_at: date) +    User.switch_email_notifications(user2, "digest", true) +    CommonAPI.post(user, %{"status" => "hey @#{user2.nickname}!"}) + +    DigestEmailWorker.perform() + +    assert_received {:email, email} +    assert email.to == [{user2.name, user2.email}] +    assert email.subject == "Your digest from #{Pleroma.Config.get(:instance)[:name]}" +  end +end diff --git a/test/web/fallback_test.exs b/test/web/fallback_test.exs index 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 0f43bc8f2..09e54533f 100644 --- a/test/web/federator_test.exs +++ b/test/web/federator_test.exs @@ -12,9 +12,27 @@ defmodule Pleroma.Web.FederatorTest do    setup_all do      Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) 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 "Publisher.perform" do +    test "call `perform` with unknown task" do +      assert { +               :error, +               "Don't know what to do with this" +             } = Pleroma.Web.Federator.Publisher.perform("test", :ok, :ok) +    end +  end +    describe "Publish an activity" do      setup do        user = insert(:user) @@ -51,8 +69,6 @@ defmodule Pleroma.Web.FederatorTest do        end        refute_received :relay_publish - -      Pleroma.Config.put([:instance, :allow_relay], true)      end    end @@ -213,5 +229,20 @@ defmodule Pleroma.Web.FederatorTest do        :error = Federator.incoming_ap_doc(params)      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 Federator.incoming_ap_doc(params) == :error +    end    end  end diff --git a/test/web/instances/instance_test.exs b/test/web/instances/instance_test.exs index d28730994..3fd011fd3 100644 --- a/test/web/instances/instance_test.exs +++ b/test/web/instances/instance_test.exs @@ -10,14 +10,8 @@ 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 diff --git a/test/web/instances/instances_test.exs b/test/web/instances/instances_test.exs index f0d84edea..dea8e2aea 100644 --- a/test/web/instances/instances_test.exs +++ b/test/web/instances/instances_test.exs @@ -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/mastodon_api/account_view_test.exs b/test/web/mastodon_api/account_view_test.exs index aaf2261bb..1d8b28339 100644 --- a/test/web/mastodon_api/account_view_test.exs +++ b/test/web/mastodon_api/account_view_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do    use Pleroma.DataCase    import Pleroma.Factory    alias Pleroma.User +  alias Pleroma.Web.CommonAPI    alias Pleroma.Web.MastodonAPI.AccountView    test "Represent a user account" do @@ -19,9 +20,18 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do        ]      } +    background_image = %{ +      "url" => [%{"href" => "https://example.com/images/asuka_hospital.png"}] +    } +      user =        insert(:user, %{ -        info: %{note_count: 5, follower_count: 3, source_data: source_data}, +        info: %{ +          note_count: 5, +          follower_count: 3, +          source_data: source_data, +          background: background_image +        },          nickname: "shp@shitposter.club",          name: ":karjalanpiirakka: shp",          bio: "<script src=\"invalid-html\"></script><span>valid html</span>", @@ -57,9 +67,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do        source: %{          note: "valid html",          sensitive: false, -        pleroma: %{} +        pleroma: %{}, +        fields: []        },        pleroma: %{ +        background_image: "https://example.com/images/asuka_hospital.png",          confirmation_pending: false,          tags: [],          is_admin: false, @@ -67,7 +79,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do          hide_favorites: true,          hide_followers: false,          hide_follows: false, -        relationship: %{} +        relationship: %{}, +        skip_thread_containment: false        }      } @@ -78,10 +91,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do      user = insert(:user)      notification_settings = %{ -      "remote" => true, -      "local" => true,        "followers" => true, -      "follows" => true +      "follows" => true, +      "non_follows" => true, +      "non_followers" => true      }      privacy = user.info.default_scope @@ -122,9 +135,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do        source: %{          note: user.bio,          sensitive: false, -        pleroma: %{} +        pleroma: %{}, +        fields: []        },        pleroma: %{ +        background_image: nil,          confirmation_pending: false,          tags: [],          is_admin: false, @@ -132,13 +147,21 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do          hide_favorites: true,          hide_followers: false,          hide_follows: false, -        relationship: %{} +        relationship: %{}, +        skip_thread_containment: false        }      }      assert expected == AccountView.render("account.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}) +    assert represented[:pleroma][:deactivated] == true +  end +    test "Represent a smaller mention" do      user = insert(:user) @@ -152,28 +175,100 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do      assert expected == AccountView.render("mention.json", %{user: user})    end -  test "represent a relationship" do -    user = insert(:user) -    other_user = insert(:user) +  describe "relationship" do +    test "represent a relationship for the following and followed user" do +      user = insert(:user) +      other_user = insert(:user) -    {:ok, user} = User.follow(user, other_user) -    {:ok, user} = User.block(user, other_user) +      {: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) -    expected = %{ -      id: to_string(other_user.id), -      following: false, -      followed_by: false, -      blocking: true, -      muting: false, -      muting_notifications: false, -      subscribing: false, -      requested: false, -      domain_blocking: false, -      showing_reblogs: true, -      endorsed: false -    } +      expected = %{ +        id: to_string(other_user.id), +        following: true, +        followed_by: true, +        blocking: false, +        blocked_by: false, +        muting: true, +        muting_notifications: true, +        subscribing: true, +        requested: false, +        domain_blocking: false, +        showing_reblogs: false, +        endorsed: false +      } + +      assert expected == +               AccountView.render("relationship.json", %{user: user, target: other_user}) +    end + +    test "represent a relationship for the blocking and blocked user" do +      user = insert(:user) +      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) + +      expected = %{ +        id: to_string(other_user.id), +        following: false, +        followed_by: false, +        blocking: true, +        blocked_by: true, +        muting: false, +        muting_notifications: false, +        subscribing: false, +        requested: false, +        domain_blocking: false, +        showing_reblogs: true, +        endorsed: false +      } + +      assert expected == +               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 -    assert expected == AccountView.render("relationship.json", %{user: user, target: other_user}) +    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}}) + +      {:ok, user, other_user, _} = CommonAPI.follow(user, other_user) +      user = User.get_cached_by_id(user.id) +      other_user = User.get_cached_by_id(other_user.id) + +      expected = %{ +        id: to_string(other_user.id), +        following: false, +        followed_by: false, +        blocking: false, +        blocked_by: false, +        muting: false, +        muting_notifications: false, +        subscribing: false, +        requested: true, +        domain_blocking: false, +        showing_reblogs: true, +        endorsed: false +      } + +      assert expected == +               AccountView.render("relationship.json", %{user: user, target: other_user}) +    end    end    test "represent an embedded relationship" do @@ -211,9 +306,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do        source: %{          note: user.bio,          sensitive: false, -        pleroma: %{} +        pleroma: %{}, +        fields: []        },        pleroma: %{ +        background_image: nil,          confirmation_pending: false,          tags: [],          is_admin: false, @@ -226,6 +323,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do            following: false,            followed_by: false,            blocking: true, +          blocked_by: false,            subscribing: false,            muting: false,            muting_notifications: false, @@ -233,10 +331,59 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do            domain_blocking: false,            showing_reblogs: true,            endorsed: false -        } +        }, +        skip_thread_containment: false        }      }      assert expected == AccountView.render("account.json", %{user: 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"}}}) + +    result = +      AccountView.render("account.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}) +    assert result.pleroma[:settings_store] == nil + +    result = AccountView.render("account.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}) +    refute result.display_name == "<marquee> username </marquee>" +  end + +  describe "hiding follows/following" do +    test "shows when follows/following are hidden and sets follower/following count to 0" do +      user = insert(:user, info: %{hide_followers: true, hide_follows: true}) +      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: true, hide_followers: true} +             } = AccountView.render("account.json", %{user: user}) +    end + +    test "shows actual follower/following count to the account owner" do +      user = insert(:user, info: %{hide_followers: true, hide_follows: true}) +      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("account.json", %{user: user, for: user}) +    end +  end  end diff --git a/test/web/mastodon_api/conversation_view_test.exs b/test/web/mastodon_api/conversation_view_test.exs new file mode 100644 index 000000000..a2a880705 --- /dev/null +++ b/test/web/mastodon_api/conversation_view_test.exs @@ -0,0 +1,34 @@ +# 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 +  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 new file mode 100644 index 000000000..87ee82050 --- /dev/null +++ b/test/web/mastodon_api/mastodon_api_controller/update_credentials_test.exs @@ -0,0 +1,369 @@ +# 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 +    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 + +    test "update fields", %{conn: conn} do +      user = insert(:user) + +      fields = [ +        %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "<script>bar</script>"}, +        %{"name" => "link", "value" => "cofe.io"} +      ] + +      account = +        conn +        |> assign(:user, user) +        |> patch("/api/v1/accounts/update_credentials", %{"fields" => fields}) +        |> json_response(200) + +      assert account["fields"] == [ +               %{"name" => "foo", "value" => "bar"}, +               %{"name" => "link", "value" => "<a href=\"http://cofe.io\">cofe.io</a>"} +             ] + +      assert account["source"]["fields"] == [ +               %{ +                 "name" => "<a href=\"http://google.com\">foo</a>", +                 "value" => "<script>bar</script>" +               }, +               %{"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 +               |> assign(:user, user) +               |> patch("/api/v1/accounts/update_credentials", %{"fields" => 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 +               |> assign(:user, user) +               |> patch("/api/v1/accounts/update_credentials", %{"fields" => 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 +               |> assign(:user, user) +               |> patch("/api/v1/accounts/update_credentials", %{"fields" => fields}) +               |> json_response(403) +    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 93ef630f2..6fcdc19aa 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -7,6 +7,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do    alias Ecto.Changeset    alias Pleroma.Activity +  alias Pleroma.ActivityExpiration +  alias Pleroma.Config    alias Pleroma.Notification    alias Pleroma.Object    alias Pleroma.Repo @@ -23,17 +25,23 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do    import Pleroma.Factory    import ExUnit.CaptureLog    import Tesla.Mock +  import Swoosh.TestAssertions + +  @image "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7"    setup do      mock(fn env -> apply(HttpRequestMock, :request, [env]) end)      :ok    end +  clear_config([:instance, :public]) +  clear_config([:rich_media, :enabled]) +    test "the home timeline", %{conn: conn} do      user = insert(:user)      following = insert(:user) -    {:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"}) +    {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"})      conn =        conn @@ -56,7 +64,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      following = insert(:user)      capture_log(fn -> -      {:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"}) +      {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"})        {:ok, [_activity]} =          OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873") @@ -82,162 +90,315 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do    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) +    Config.put([:instance, :public], false)      assert conn             |> get("/api/v1/timelines/public", %{"local" => "False"})             |> json_response(403) == %{"error" => "This resource requires authentication."}    end -  test "posting a status", %{conn: conn} do -    user = insert(:user) +  describe "posting statuses" do +    setup do +      user = insert(:user) -    idempotency_key = "Pikachu rocks!" +      conn = +        build_conn() +        |> assign(:user, user) -    conn_one = -      conn -      |> assign(:user, user) -      |> put_req_header("idempotency-key", idempotency_key) -      |> post("/api/v1/statuses", %{ -        "status" => "cofe", -        "spoiler_text" => "2hu", -        "sensitive" => "false" -      }) +      [conn: conn] +    end -    {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key) -    # Six hours -    assert ttl > :timer.seconds(6 * 60 * 60 - 1) +    test "posting a status", %{conn: conn} do +      idempotency_key = "Pikachu rocks!" -    assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} = -             json_response(conn_one, 200) +      conn_one = +        conn +        |> put_req_header("idempotency-key", idempotency_key) +        |> post("/api/v1/statuses", %{ +          "status" => "cofe", +          "spoiler_text" => "2hu", +          "sensitive" => "false" +        }) -    assert Activity.get_by_id(id) +      {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key) +      # Six hours +      assert ttl > :timer.seconds(6 * 60 * 60 - 1) -    conn_two = -      conn -      |> assign(:user, user) -      |> put_req_header("idempotency-key", idempotency_key) -      |> post("/api/v1/statuses", %{ -        "status" => "cofe", -        "spoiler_text" => "2hu", -        "sensitive" => "false" -      }) +      assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} = +               json_response(conn_one, 200) -    assert %{"id" => second_id} = json_response(conn_two, 200) +      assert Activity.get_by_id(id) -    assert id == second_id +      conn_two = +        conn +        |> put_req_header("idempotency-key", idempotency_key) +        |> post("/api/v1/statuses", %{ +          "status" => "cofe", +          "spoiler_text" => "2hu", +          "sensitive" => "false" +        }) -    conn_three = -      conn -      |> assign(:user, user) -      |> post("/api/v1/statuses", %{ -        "status" => "cofe", -        "spoiler_text" => "2hu", -        "sensitive" => "false" -      }) +      assert %{"id" => second_id} = json_response(conn_two, 200) +      assert id == second_id -    assert %{"id" => third_id} = json_response(conn_three, 200) +      conn_three = +        conn +        |> post("/api/v1/statuses", %{ +          "status" => "cofe", +          "spoiler_text" => "2hu", +          "sensitive" => "false" +        }) -    refute id == third_id -  end +      assert %{"id" => third_id} = json_response(conn_three, 200) +      refute id == third_id -  test "posting a sensitive status", %{conn: conn} do -    user = insert(:user) +      # An activity that will expire: +      # 2 hours +      expires_in = 120 * 60 -    conn = -      conn -      |> assign(:user, user) -      |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true}) +      conn_four = +        conn +        |> post("api/v1/statuses", %{ +          "status" => "oolong", +          "expires_in" => expires_in +        }) -    assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200) -    assert Activity.get_by_id(id) -  end +      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) -  test "posting a fake status", %{conn: conn} do -    user = insert(:user) +      estimated_expires_at = +        NaiveDateTime.utc_now() +        |> NaiveDateTime.add(expires_in) +        |> NaiveDateTime.truncate(:second) -    real_conn = -      conn -      |> assign(:user, user) -      |> 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" -      }) +      # 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 -    real_status = json_response(real_conn, 200) +      assert fourth_response["pleroma"]["expires_at"] == +               NaiveDateTime.to_iso8601(expiration.scheduled_at) +    end -    assert real_status -    assert Object.get_by_ap_id(real_status["uri"]) +    test "replying to a status", %{conn: conn} do +      user = insert(:user) +      {:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"}) -    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) +      conn = +        conn +        |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id}) -    fake_conn = -      conn -      |> assign(:user, user) -      |> 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 -      }) +      assert %{"content" => "xD", "id" => id} = json_response(conn, 200) -    fake_status = json_response(fake_conn, 200) +      activity = Activity.get_by_id(id) -    assert fake_status -    refute Object.get_by_ap_id(fake_status["uri"]) +      assert activity.data["context"] == replied_to.data["context"] +      assert Activity.get_in_reply_to_activity(activity).id == replied_to.id +    end -    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) +    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"}) -    assert real_status == fake_status -  end +      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 +          }) -  test "posting a status with OGP link preview", %{conn: conn} do -    Pleroma.Config.put([:rich_media, :enabled], true) -    user = insert(:user) +        assert json_response(conn, 422) == %{"error" => "The message visibility must be direct"} +      end) +    end -    conn = -      conn -      |> assign(:user, user) -      |> post("/api/v1/statuses", %{ -        "status" => "http://example.com/ogp" -      }) +    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"]) -    assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200) -    assert Activity.get_by_id(id) -    Pleroma.Config.put([:rich_media, :enabled], false) +      fake_status = +        fake_status +        |> Map.put("id", nil) +        |> Map.put("url", nil) +        |> Map.put("uri", nil) +        |> Map.put("created_at", nil) +        |> Kernel.put_in(["pleroma", "conversation_id"], nil) + +      assert real_status == fake_status +    end + +    test "posting a status with OGP link preview", %{conn: conn} do +      Config.put([:rich_media, :enabled], true) + +      conn = +        conn +        |> post("/api/v1/statuses", %{ +          "status" => "https://example.com/ogp" +        }) + +      assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200) +      assert Activity.get_by_id(id) +    end + +    test "posting a direct status", %{conn: conn} do +      user2 = insert(:user) +      content = "direct cofe @#{user2.nickname}" + +      conn = +        conn +        |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"}) + +      assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200) +      assert activity = Activity.get_by_id(id) +      assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id] +      assert activity.data["to"] == [user2.ap_id] +      assert activity.data["cc"] == [] +    end    end -  test "posting a direct status", %{conn: conn} do -    user1 = insert(:user) -    user2 = insert(:user) -    content = "direct cofe @#{user2.nickname}" +  describe "posting polls" do +    test "posting a poll", %{conn: conn} do +      user = insert(:user) +      time = NaiveDateTime.utc_now() -    conn = -      conn -      |> assign(:user, user1) -      |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"}) +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "status" => "Who is the #bestgrill?", +          "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420} +        }) + +      response = json_response(conn, 200) -    assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200) -    assert activity = Activity.get_by_id(id) -    assert activity.recipients == [user2.ap_id, user1.ap_id] -    assert activity.data["to"] == [user2.ap_id] -    assert activity.data["cc"] == [] +      assert Enum.all?(response["poll"]["options"], fn %{"title" => title} -> +               title in ["Rei", "Asuka", "Misato"] +             end) + +      assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430 +      refute response["poll"]["expred"] +    end + +    test "option limit is enforced", %{conn: conn} do +      user = insert(:user) +      limit = Config.get([:instance, :poll_limits, :max_options]) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "status" => "desu~", +          "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1} +        }) + +      %{"error" => error} = json_response(conn, 422) +      assert error == "Poll can't contain more than #{limit} options" +    end + +    test "option character limit is enforced", %{conn: conn} do +      user = insert(:user) +      limit = Config.get([:instance, :poll_limits, :max_option_chars]) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "status" => "...", +          "poll" => %{ +            "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)], +            "expires_in" => 1 +          } +        }) + +      %{"error" => error} = json_response(conn, 422) +      assert error == "Poll options cannot be longer than #{limit} characters each" +    end + +    test "minimal date limit is enforced", %{conn: conn} do +      user = insert(:user) +      limit = Config.get([:instance, :poll_limits, :min_expiration]) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "status" => "imagine arbitrary limits", +          "poll" => %{ +            "options" => ["this post was made by pleroma gang"], +            "expires_in" => limit - 1 +          } +        }) + +      %{"error" => error} = json_response(conn, 422) +      assert error == "Expiration date is too soon" +    end + +    test "maximum date limit is enforced", %{conn: conn} do +      user = insert(:user) +      limit = Config.get([:instance, :poll_limits, :max_expiration]) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "status" => "imagine arbitrary limits", +          "poll" => %{ +            "options" => ["this post was made by pleroma gang"], +            "expires_in" => limit + 1 +          } +        }) + +      %{"error" => error} = json_response(conn, 422) +      assert error == "Expiration date is too far in the future" +    end    end    test "direct timeline", %{conn: conn} do @@ -269,7 +430,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert %{"visibility" => "direct"} = status      assert status["url"] != direct.data["id"] -    # User should be able to see his own direct message +    # User should be able to see their own direct message      res_conn =        build_conn()        |> assign(:user, user_one) @@ -317,12 +478,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do    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}!", +        "status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!",          "visibility" => "direct"        }) @@ -348,7 +510,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do               }             ] = 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 @@ -400,81 +565,146 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert status["id"] == direct.id    end -  test "replying to a status", %{conn: conn} do +  test "verify_credentials", %{conn: conn} do      user = insert(:user) -    {:ok, replied_to} = TwitterAPI.create_status(user, %{"status" => "cofe"}) +    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) -      |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id}) +      |> get("/api/v1/accounts/verify_credentials") -    assert %{"content" => "xD", "id" => id} = json_response(conn, 200) +    assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200) +    assert id == to_string(user.id) +  end -    activity = Activity.get_by_id(id) +  test "apps/verify_credentials", %{conn: conn} do +    token = insert(:oauth_token) -    assert activity.data["context"] == replied_to.data["context"] -    assert Activity.get_in_reply_to_activity(activity).id == replied_to.id +    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 "posting a status with an invalid in_reply_to_id", %{conn: conn} do +  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) -      |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""}) +      |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: avatar_image}) -    assert %{"content" => "xD", "id" => id} = json_response(conn, 200) +    user = refresh_record(user) -    activity = Activity.get_by_id(id) +    assert %{ +             "name" => _, +             "type" => _, +             "url" => [ +               %{ +                 "href" => _, +                 "mediaType" => _, +                 "type" => _ +               } +             ] +           } = user.avatar -    assert activity +    assert %{"url" => _} = json_response(conn, 200)    end -  test "verify_credentials", %{conn: conn} do +  test "user avatar can be reset", %{conn: conn} do      user = insert(:user)      conn =        conn        |> assign(:user, user) -      |> get("/api/v1/accounts/verify_credentials") +      |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: ""}) -    assert %{"id" => id, "source" => %{"privacy" => "public"}} = json_response(conn, 200) -    assert id == to_string(user.id) +    user = User.get_cached_by_id(user.id) + +    assert user.avatar == nil + +    assert %{"url" => nil} = json_response(conn, 200)    end -  test "verify_credentials default scope unlisted", %{conn: conn} do -    user = insert(:user, %{info: %User.Info{default_scope: "unlisted"}}) +  test "can set profile banner", %{conn: conn} do +    user = insert(:user)      conn =        conn        |> assign(:user, user) -      |> get("/api/v1/accounts/verify_credentials") +      |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => @image}) -    assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200) -    assert id == to_string(user.id) +    user = refresh_record(user) +    assert user.info.banner["type"] == "Image" + +    assert %{"url" => _} = json_response(conn, 200)    end -  test "apps/verify_credentials", %{conn: conn} do -    token = insert(:oauth_token) +  test "can reset profile banner", %{conn: conn} do +    user = insert(:user)      conn =        conn -      |> assign(:user, token.user) -      |> assign(:token, token) -      |> get("/api/v1/apps/verify_credentials") +      |> assign(:user, user) +      |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => ""}) -    app = Repo.preload(token, :app).app +    user = refresh_record(user) +    assert user.info.banner == %{} -    expected = %{ -      "name" => app.client_name, -      "website" => app.website, -      "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key) -    } +    assert %{"url" => nil} = json_response(conn, 200) +  end -    assert expected == json_response(conn, 200) +  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 @@ -800,8 +1030,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      test "list timeline", %{conn: conn} do        user = insert(:user)        other_user = insert(:user) -      {:ok, _activity_one} = TwitterAPI.create_status(user, %{"status" => "Marisa is cute."}) -      {:ok, activity_two} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."}) +      {: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) @@ -818,10 +1048,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      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} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."}) +      {:ok, activity_one} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."})        {:ok, _activity_two} = -        TwitterAPI.create_status(other_user, %{ +        CommonAPI.post(other_user, %{            "status" => "Marisa is cute.",            "visibility" => "private"          }) @@ -845,8 +1075,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        user = insert(:user)        other_user = insert(:user) -      {:ok, activity} = -        TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"}) +      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})        {:ok, [_notification]} = Notification.create_notifications(activity) @@ -868,8 +1097,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        user = insert(:user)        other_user = insert(:user) -      {:ok, activity} = -        TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"}) +      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})        {:ok, [notification]} = Notification.create_notifications(activity) @@ -891,8 +1119,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        user = insert(:user)        other_user = insert(:user) -      {:ok, activity} = -        TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"}) +      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})        {:ok, [notification]} = Notification.create_notifications(activity) @@ -908,8 +1135,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        user = insert(:user)        other_user = insert(:user) -      {:ok, activity} = -        TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"}) +      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})        {:ok, [_notification]} = Notification.create_notifications(activity) @@ -1070,6 +1296,71 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        result = json_response(conn_res, 200)        assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result      end + +    test "doesn't see notifications after muting user with notifications", %{conn: conn} do +      user = insert(:user) +      user2 = insert(:user) + +      {:ok, _, _, _} = CommonAPI.follow(user, user2) +      {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"}) + +      conn = assign(conn, :user, user) + +      conn = get(conn, "/api/v1/notifications") + +      assert length(json_response(conn, 200)) == 1 + +      {:ok, user} = User.mute(user, user2) + +      conn = assign(build_conn(), :user, user) +      conn = get(conn, "/api/v1/notifications") + +      assert json_response(conn, 200) == [] +    end + +    test "see notifications after muting user without notifications", %{conn: conn} do +      user = insert(:user) +      user2 = insert(:user) + +      {:ok, _, _, _} = CommonAPI.follow(user, user2) +      {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"}) + +      conn = assign(conn, :user, user) + +      conn = get(conn, "/api/v1/notifications") + +      assert length(json_response(conn, 200)) == 1 + +      {:ok, user} = User.mute(user, user2, false) + +      conn = assign(build_conn(), :user, user) +      conn = get(conn, "/api/v1/notifications") + +      assert length(json_response(conn, 200)) == 1 +    end + +    test "see notifications after muting user with notifications and with_muted parameter", %{ +      conn: conn +    } do +      user = insert(:user) +      user2 = insert(:user) + +      {:ok, _, _, _} = CommonAPI.follow(user, user2) +      {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"}) + +      conn = assign(conn, :user, user) + +      conn = get(conn, "/api/v1/notifications") + +      assert length(json_response(conn, 200)) == 1 + +      {:ok, user} = User.mute(user, user2) + +      conn = assign(build_conn(), :user, user) +      conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"}) + +      assert length(json_response(conn, 200)) == 1 +    end    end    describe "reblogging" do @@ -1126,6 +1417,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        assert to_string(activity.id) == id      end + +    test "returns 400 error when activity is not exist", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses/foo/reblog") + +      assert json_response(conn, 400) == %{"error" => "Could not repeat"} +    end    end    describe "unreblogging" do @@ -1144,6 +1446,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        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 @@ -1162,16 +1475,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        assert to_string(activity.id) == id      end -    test "returns 500 for a wrong id", %{conn: conn} do +    test "returns 400 error for a wrong id", %{conn: conn} do        user = insert(:user) -      resp = +      conn =          conn          |> assign(:user, user)          |> post("/api/v1/statuses/1/favourite") -        |> json_response(500) -      assert resp == "Something went wrong" +      assert json_response(conn, 400) == %{"error" => "Could not favorite"}      end    end @@ -1192,6 +1504,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        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 @@ -1262,10 +1585,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        media =          TwitterAPI.upload(file, user, "json") -        |> Poison.decode!() +        |> Jason.decode!()        {:ok, image_post} = -        TwitterAPI.create_status(user, %{"status" => "cofe", "media_ids" => [media["media_id"]]}) +        CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media["media_id"]]})        conn =          conn @@ -1301,6 +1624,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        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 @@ -1320,6 +1656,43 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      end    end +  describe "media upload" do +    setup do +      user = insert(:user) + +      conn = +        build_conn() +        |> assign(:user, user) + +      image = %Plug.Upload{ +        content_type: "image/jpg", +        path: Path.absname("test/fixtures/image.jpg"), +        filename: "an_image.jpg" +      } + +      [conn: conn, image: image] +    end + +    clear_config([:media_proxy]) +    clear_config([Pleroma.Upload]) + +    test "returns uploaded image", %{conn: conn, image: image} do +      desc = "Description of the image" + +      media = +        conn +        |> post("/api/v1/media", %{"file" => image, "description" => desc}) +        |> json_response(:ok) + +      assert media["type"] == "image" +      assert media["description"] == desc +      assert media["id"] + +      object = Repo.get(Object, media["id"]) +      assert object.data["actor"] == User.ap_id(conn.assigns[:user]) +    end +  end +    describe "locked accounts" do      test "/api/v1/follow_requests works" do        user = insert(:user, %{info: %User.Info{locked: true}}) @@ -1429,32 +1802,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert id == user.id    end -  test "media upload", %{conn: conn} do -    file = %Plug.Upload{ -      content_type: "image/jpg", -      path: Path.absname("test/fixtures/image.jpg"), -      filename: "an_image.jpg" -    } - -    desc = "Description of the image" - -    user = insert(:user) - -    conn = -      conn -      |> assign(:user, user) -      |> post("/api/v1/media", %{"file" => file, "description" => desc}) - -    assert media = json_response(conn, 200) - -    assert media["type"] == "image" -    assert media["description"] == desc -    assert media["id"] - -    object = Repo.get(Object, media["id"]) -    assert object.data["actor"] == User.ap_id(user) -  end -    test "mascot upload", %{conn: conn} do      user = insert(:user) @@ -1525,7 +1872,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      following = insert(:user)      capture_log(fn -> -      {:ok, activity} = TwitterAPI.create_status(following, %{"status" => "test #2hu"}) +      {:ok, activity} = CommonAPI.post(following, %{"status" => "test #2hu"})        {:ok, [_activity]} =          OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873") @@ -1838,25 +2185,52 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert %{"error" => "Record not found"} = json_response(conn_res, 404)    end -  test "muting / unmuting a user", %{conn: conn} do -    user = insert(:user) -    other_user = insert(:user) +  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") +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/accounts/#{other_user.id}/mute") -    assert %{"id" => _id, "muting" => true} = json_response(conn, 200) +      response = json_response(conn, 200) -    user = User.get_cached_by_id(user.id) +      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") +      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" => false} = 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 @@ -1983,104 +2357,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      end)    end -  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) - -    result_ids = for result <- results, do: result["acct"] - -    assert user_two.nickname in result_ids -    assert user_three.nickname in result_ids - -    results = -      conn -      |> assign(:user, user) -      |> get("/api/v1/accounts/search", %{"q" => "2hu"}) -      |> json_response(200) - -    result_ids = for result <- results, do: result["acct"] - -    assert user_three.nickname in result_ids -  end - -  test "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"}) - -    {:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu"}) - -    {:ok, _activity} = -      CommonAPI.post(user, %{ -        "status" => "This is about 2hu, but private", -        "visibility" => "private" -      }) - -    {:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"}) - -    conn = -      conn -      |> get("/api/v1/search", %{"q" => "2hu"}) - -    assert results = json_response(conn, 200) - -    [account | _] = results["accounts"] -    assert account["id"] == to_string(user_three.id) - -    assert results["hashtags"] == [] - -    [status] = results["statuses"] -    assert status["id"] == to_string(activity.id) -  end - -  test "search fetches remote statuses", %{conn: conn} do -    capture_log(fn -> -      conn = -        conn -        |> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"}) - -      assert results = json_response(conn, 200) - -      [status] = results["statuses"] -      assert status["uri"] == "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment" -    end) -  end - -  test "search doesn't show statuses that it shouldn't", %{conn: conn} do -    {:ok, activity} = -      CommonAPI.post(insert(:user), %{ -        "status" => "This is about 2hu, but private", -        "visibility" => "private" -      }) - -    capture_log(fn -> -      conn = -        conn -        |> get("/api/v1/search", %{"q" => Object.normalize(activity).data["id"]}) - -      assert results = json_response(conn, 200) - -      [] = results["statuses"] -    end) -  end - -  test "search fetches remote accounts", %{conn: conn} do -    conn = -      conn -      |> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "true"}) - -    assert results = json_response(conn, 200) -    [account] = results["accounts"] -    assert account["acct"] == "shp@social.heldscal.la" -  end -    test "returns the favorites of a user", %{conn: conn} do      user = insert(:user)      other_user = insert(:user) @@ -2321,210 +2597,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      end    end -  describe "updating credentials" do -    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 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 "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 -    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]) +    email = Config.get([:instance, :email])      # Note: not checking for "max_toot_chars" since it's optional      assert %{               "uri" => _, @@ -2538,7 +2615,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do               "stats" => _,               "thumbnail" => _,               "languages" => _, -             "registrations" => _ +             "registrations" => _, +             "poll_limits" => _             } = result      assert email == from_config_email @@ -2553,7 +2631,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      insert(:user, %{local: false, nickname: "u@peer1.com"})      insert(:user, %{local: false, nickname: "u@peer2.com"}) -    {:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"}) +    {: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) @@ -2565,7 +2643,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        |> Changeset.put_embed(:info, info_change)        |> User.update_and_set_cache() -    Pleroma.Stats.update_stats() +    Pleroma.Stats.force_update()      conn = get(conn, "/api/v1/instance") @@ -2583,7 +2661,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      insert(:user, %{local: false, nickname: "u@peer1.com"})      insert(:user, %{local: false, nickname: "u@peer2.com"}) -    Pleroma.Stats.update_stats() +    Pleroma.Stats.force_update()      conn = get(conn, "/api/v1/instance/peers") @@ -2608,14 +2686,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do    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 +    clear_config([:instance, :max_pinned_statuses]) do +      Config.put([:instance, :max_pinned_statuses], 1) +    end +      test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do        {:ok, _} = CommonAPI.pin(activity.id, user) @@ -2646,6 +2726,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do                 |> json_response(200)      end +    test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do +      {:ok, dm} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"}) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses/#{dm.id}/pin") + +      assert json_response(conn, 400) == %{"error" => "Could not pin"} +    end +      test "unpin status", %{conn: conn, user: user, activity: activity} do        {:ok, _} = CommonAPI.pin(activity.id, user) @@ -2665,6 +2756,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do                 |> 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!!!"}) @@ -2688,26 +2788,22 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do    describe "cards" do      setup do -      Pleroma.Config.put([:rich_media, :enabled], true) - -      on_exit(fn -> -        Pleroma.Config.put([:rich_media, :enabled], false) -      end) +      Config.put([:rich_media, :enabled], true)        user = insert(:user)        %{user: user}      end      test "returns rich-media card", %{conn: conn, user: user} do -      {:ok, activity} = CommonAPI.post(user, %{"status" => "http://example.com/ogp"}) +      {: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", +        "provider_name" => "example.com", +        "provider_url" => "https://example.com",          "title" => "The Rock",          "type" => "link", -        "url" => "http://www.imdb.com/title/tt0117500/", +        "url" => "https://example.com/ogp",          "description" =>            "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",          "pleroma" => %{ @@ -2715,7 +2811,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do              "image" => "http://ia.media-imdb.com/images/rock.jpg",              "title" => "The Rock",              "type" => "video.movie", -            "url" => "http://www.imdb.com/title/tt0117500/", +            "url" => "https://example.com/ogp",              "description" =>                "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."            } @@ -2731,7 +2827,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        # works with private posts        {:ok, activity} = -        CommonAPI.post(user, %{"status" => "http://example.com/ogp", "visibility" => "direct"}) +        CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"})        response_two =          conn @@ -2743,7 +2839,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      end      test "replaces missing description with an empty string", %{conn: conn, user: user} do -      {:ok, activity} = CommonAPI.post(user, %{"status" => "http://example.com/ogp-missing-data"}) +      {:ok, activity} = +        CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"})        response =          conn @@ -2755,14 +2852,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do                 "title" => "Pleroma",                 "description" => "",                 "image" => nil, -               "provider_name" => "pleroma.social", -               "provider_url" => "https://pleroma.social", -               "url" => "https://pleroma.social/", +               "provider_name" => "example.com", +               "provider_url" => "https://example.com", +               "url" => "https://example.com/ogp-missing-data",                 "pleroma" => %{                   "opengraph" => %{                     "title" => "Pleroma",                     "type" => "website", -                   "url" => "https://pleroma.social/" +                   "url" => "https://example.com/ogp-missing-data"                   }                 }               } @@ -2822,8 +2919,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do    describe "conversation muting" do      setup do +      post_user = insert(:user)        user = insert(:user) -      {:ok, activity} = CommonAPI.post(user, %{"status" => "HIE"}) + +      {:ok, activity} = CommonAPI.post(post_user, %{"status" => "HIE"})        [user: user, activity: activity]      end @@ -2838,6 +2937,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do                 |> 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) @@ -2852,31 +2962,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      end    end -  test "flavours switching (Pleroma Extension)", %{conn: conn} do -    user = insert(:user) - -    get_old_flavour = -      conn -      |> assign(:user, user) -      |> get("/api/v1/pleroma/flavour") - -    assert "glitch" == json_response(get_old_flavour, 200) - -    set_flavour = -      conn -      |> assign(:user, user) -      |> post("/api/v1/pleroma/flavour/vanilla") - -    assert "vanilla" == json_response(set_flavour, 200) - -    get_new_flavour = -      conn -      |> assign(:user, user) -      |> post("/api/v1/pleroma/flavour/vanilla") - -    assert json_response(set_flavour, 200) == json_response(get_new_flavour, 200) -  end -    describe "reports" do      setup do        reporter = insert(:user) @@ -2907,7 +2992,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do                 |> post("/api/v1/reports", %{                   "account_id" => target_user.id,                   "status_ids" => [activity.id], -                 "comment" => "bad status!" +                 "comment" => "bad status!", +                 "forward" => "false"                 })                 |> json_response(200)      end @@ -2929,7 +3015,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        reporter: reporter,        target_user: target_user      } do -      max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000) +      max_size = Config.get([:instance, :max_report_comment_size], 1000)        comment = String.pad_trailing("a", max_size + 1, "a")        error = %{"error" => "Comment must be up to #{max_size} characters"} @@ -2940,6 +3026,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do                 |> 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 @@ -3011,6 +3110,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        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 @@ -3040,6 +3140,18 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        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) @@ -3298,7 +3410,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      user2 = insert(:user)      user3 = insert(:user) -    {:ok, replied_to} = TwitterAPI.create_status(user1, %{"status" => "cofe"}) +    {:ok, replied_to} = CommonAPI.post(user1, %{"status" => "cofe"})      # Reply to status from another user      conn1 = @@ -3339,24 +3451,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do    end    describe "create account by app" do -    setup do -      enabled = Pleroma.Config.get([:app_account_creation, :enabled]) -      max_requests = Pleroma.Config.get([:app_account_creation, :max_requests]) -      interval = Pleroma.Config.get([:app_account_creation, :interval]) - -      Pleroma.Config.put([:app_account_creation, :enabled], true) -      Pleroma.Config.put([:app_account_creation, :max_requests], 5) -      Pleroma.Config.put([:app_account_creation, :interval], 1) - -      on_exit(fn -> -        Pleroma.Config.put([:app_account_creation, :enabled], enabled) -        Pleroma.Config.put([:app_account_creation, :max_requests], max_requests) -        Pleroma.Config.put([:app_account_creation, :interval], interval) -      end) - -      :ok -    end -      test "Account registration via Application", %{conn: conn} do        conn =          conn @@ -3459,7 +3553,489 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do            agreement: true          }) -      assert json_response(conn, 403) == %{"error" => "Rate limit exceeded."} +      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 + +    test "does not return users who have favorited the status but are blocked", %{ +      conn: %{assigns: %{user: user}} = conn, +      activity: activity +    } do +      other_user = insert(:user) +      {:ok, user} = User.block(user, other_user) + +      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + +      response = +        conn +        |> assign(:user, user) +        |> get("/api/v1/statuses/#{activity.id}/favourited_by") +        |> json_response(:ok) + +      assert Enum.empty?(response) +    end + +    test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do +      other_user = insert(:user) +      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + +      response = +        conn +        |> assign(:user, nil) +        |> get("/api/v1/statuses/#{activity.id}/favourited_by") +        |> json_response(:ok) + +      [%{"id" => id}] = response +      assert id == other_user.id +    end +  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 + +    test "does not return users who have reblogged the status but are blocked", %{ +      conn: %{assigns: %{user: user}} = conn, +      activity: activity +    } do +      other_user = insert(:user) +      {:ok, user} = User.block(user, other_user) + +      {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) + +      response = +        conn +        |> assign(:user, user) +        |> get("/api/v1/statuses/#{activity.id}/reblogged_by") +        |> json_response(:ok) + +      assert Enum.empty?(response) +    end + +    test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do +      other_user = insert(:user) +      {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) + +      response = +        conn +        |> assign(:user, nil) +        |> get("/api/v1/statuses/#{activity.id}/reblogged_by") +        |> json_response(:ok) + +      [%{"id" => id}] = response +      assert id == other_user.id +    end +  end + +  describe "POST /auth/password, with valid parameters" do +    setup %{conn: conn} do +      user = insert(:user) +      conn = post(conn, "/auth/password?email=#{user.email}") +      %{conn: conn, user: user} +    end + +    test "it returns 204", %{conn: conn} do +      assert json_response(conn, :no_content) +    end + +    test "it creates a PasswordResetToken record for user", %{user: user} do +      token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) +      assert token_record +    end + +    test "it sends an email to user", %{user: user} do +      token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) + +      email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token) +      notify_email = Config.get([:instance, :notify_email]) +      instance_name = Config.get([:instance, :name]) + +      assert_email_sent( +        from: {instance_name, notify_email}, +        to: {user.name, user.email}, +        html_body: email.html_body +      ) +    end +  end + +  describe "POST /auth/password, with invalid parameters" do +    setup do +      user = insert(:user) +      {:ok, user: user} +    end + +    test "it returns 404 when user is not found", %{conn: conn, user: user} do +      conn = post(conn, "/auth/password?email=nonexisting_#{user.email}") +      assert conn.status == 404 +      assert conn.resp_body == "" +    end + +    test "it returns 400 when user is not local", %{conn: conn, user: user} do +      {:ok, user} = Repo.update(Changeset.change(user, local: false)) +      conn = post(conn, "/auth/password?email=#{user.email}") +      assert conn.status == 400 +      assert conn.resp_body == "" +    end +  end + +  describe "POST /api/v1/pleroma/accounts/confirmation_resend" do +    setup do +      user = insert(:user) +      info_change = User.Info.confirmation_changeset(user.info, need_confirmation: true) + +      {:ok, user} = +        user +        |> Changeset.change() +        |> Changeset.put_embed(:info, info_change) +        |> Repo.update() + +      assert user.info.confirmation_pending + +      [user: user] +    end + +    clear_config([:instance, :account_activation_required]) do +      Config.put([:instance, :account_activation_required], true) +    end + +    test "resend account confirmation email", %{conn: conn, user: user} do +      conn +      |> assign(:user, user) +      |> post("/api/v1/pleroma/accounts/confirmation_resend?email=#{user.email}") +      |> json_response(:no_content) + +      email = Pleroma.Emails.UserEmail.account_confirmation_email(user) +      notify_email = Config.get([:instance, :notify_email]) +      instance_name = Config.get([:instance, :name]) + +      assert_email_sent( +        from: {instance_name, notify_email}, +        to: {user.name, user.email}, +        html_body: email.html_body +      ) +    end +  end + +  describe "GET /api/v1/suggestions" do +    setup do +      user = insert(:user) +      other_user = insert(:user) +      host = Config.get([Pleroma.Web.Endpoint, :url, :host]) +      url500 = "http://test500?#{host}&#{user.nickname}" +      url200 = "http://test200?#{host}&#{user.nickname}" + +      mock(fn +        %{method: :get, url: ^url500} -> +          %Tesla.Env{status: 500, body: "bad request"} + +        %{method: :get, url: ^url200} -> +          %Tesla.Env{ +            status: 200, +            body: +              ~s([{"acct":"yj455","avatar":"https://social.heldscal.la/avatar/201.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/201.jpeg"}, {"acct":"#{ +                other_user.ap_id +              }","avatar":"https://social.heldscal.la/avatar/202.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/202.jpeg"}]) +          } +      end) + +      [user: user, other_user: other_user] +    end + +    clear_config(:suggestions) + +    test "returns empty result when suggestions disabled", %{conn: conn, user: user} do +      Config.put([:suggestions, :enabled], false) + +      res = +        conn +        |> assign(:user, user) +        |> get("/api/v1/suggestions") +        |> json_response(200) + +      assert res == [] +    end + +    test "returns error", %{conn: conn, user: user} do +      Config.put([:suggestions, :enabled], true) +      Config.put([:suggestions, :third_party_engine], "http://test500?{{host}}&{{user}}") + +      res = +        conn +        |> assign(:user, user) +        |> get("/api/v1/suggestions") +        |> json_response(500) + +      assert res == "Something went wrong" +    end + +    test "returns suggestions", %{conn: conn, user: user, other_user: other_user} do +      Config.put([:suggestions, :enabled], true) +      Config.put([:suggestions, :third_party_engine], "http://test200?{{host}}&{{user}}") + +      res = +        conn +        |> assign(:user, user) +        |> get("/api/v1/suggestions") +        |> json_response(200) + +      assert res == [ +               %{ +                 "acct" => "yj455", +                 "avatar" => "https://social.heldscal.la/avatar/201.jpeg", +                 "avatar_static" => "https://social.heldscal.la/avatar/s/201.jpeg", +                 "id" => 0 +               }, +               %{ +                 "acct" => other_user.ap_id, +                 "avatar" => "https://social.heldscal.la/avatar/202.jpeg", +                 "avatar_static" => "https://social.heldscal.la/avatar/s/202.jpeg", +                 "id" => other_user.id +               } +             ]      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..b4c0427c9 --- /dev/null +++ b/test/web/mastodon_api/mastodon_api_test.exs @@ -0,0 +1,103 @@ +# 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.MastodonAPI.MastodonAPI +  alias Pleroma.Web.TwitterAPI.TwitterAPI + +  import Pleroma.Factory + +  describe "follow/3" do +    test "returns error when user deactivated" do +      follower = insert(:user) +      user = insert(:user, local: true, info: %{deactivated: true}) +      {:error, error} = MastodonAPI.follow(follower, user) +      assert error == "Could not follow user: You are 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} = TwitterAPI.create_status(user, %{"status" => "Akariiiin"}) +      {:ok, status1} = TwitterAPI.create_status(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/search_controller_test.exs b/test/web/mastodon_api/search_controller_test.exs new file mode 100644 index 000000000..49c79ff0a --- /dev/null +++ b/test/web/mastodon_api/search_controller_test.exs @@ -0,0 +1,285 @@ +# 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.SearchControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Object +  alias Pleroma.Web +  alias Pleroma.Web.CommonAPI +  import Pleroma.Factory +  import ExUnit.CaptureLog +  import Tesla.Mock +  import Mock + +  setup do +    mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) +    :ok +  end + +  describe ".search2" do +    test "it returns empty result if user or status search return undefined error", %{conn: conn} do +      with_mocks [ +        {Pleroma.User, [], [search: fn _q, _o -> raise "Oops" end]}, +        {Pleroma.Activity, [], [search: fn _u, _q, _o -> raise "Oops" end]} +      ] do +        capture_log(fn -> +          results = +            conn +            |> get("/api/v2/search", %{"q" => "2hu"}) +            |> json_response(200) + +          assert results["accounts"] == [] +          assert results["statuses"] == [] +        end) =~ +          "[error] Elixir.Pleroma.Web.MastodonAPI.SearchController search error: %RuntimeError{message: \"Oops\"}" +      end +    end + +    test "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"}) + +      {:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu private"}) + +      {:ok, _activity} = +        CommonAPI.post(user, %{ +          "status" => "This is about 2hu, but private", +          "visibility" => "private" +        }) + +      {:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"}) + +      conn = get(conn, "/api/v2/search", %{"q" => "2hu #private"}) + +      assert results = json_response(conn, 200) + +      [account | _] = results["accounts"] +      assert account["id"] == to_string(user_three.id) + +      assert results["hashtags"] == [ +               %{"name" => "private", "url" => "#{Web.base_url()}/tag/private"} +             ] + +      [status] = results["statuses"] +      assert status["id"] == to_string(activity.id) +    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) + +      result_ids = for result <- results, do: result["acct"] + +      assert user_two.nickname in result_ids +      assert user_three.nickname in result_ids + +      results = +        conn +        |> assign(:user, user) +        |> get("/api/v1/accounts/search", %{"q" => "2hu"}) +        |> json_response(200) + +      result_ids = for result <- results, do: result["acct"] + +      assert user_three.nickname in result_ids +    end + +    test "returns account if query contains a space", %{conn: conn} do +      user = insert(:user, %{nickname: "shp@shitposter.club"}) + +      results = +        conn +        |> assign(:user, user) +        |> get("/api/v1/accounts/search", %{"q" => "shp@shitposter.club xxx "}) +        |> json_response(200) + +      assert length(results) == 1 +    end +  end + +  describe ".search" do +    test "it returns empty result if user or status search return undefined error", %{conn: conn} do +      with_mocks [ +        {Pleroma.User, [], [search: fn _q, _o -> raise "Oops" end]}, +        {Pleroma.Activity, [], [search: fn _u, _q, _o -> raise "Oops" end]} +      ] do +        capture_log(fn -> +          results = +            conn +            |> get("/api/v1/search", %{"q" => "2hu"}) +            |> json_response(200) + +          assert results["accounts"] == [] +          assert results["statuses"] == [] +        end) =~ +          "[error] Elixir.Pleroma.Web.MastodonAPI.SearchController search error: %RuntimeError{message: \"Oops\"}" +      end +    end + +    test "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"}) + +      {:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu"}) + +      {:ok, _activity} = +        CommonAPI.post(user, %{ +          "status" => "This is about 2hu, but private", +          "visibility" => "private" +        }) + +      {:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"}) + +      conn = +        conn +        |> get("/api/v1/search", %{"q" => "2hu"}) + +      assert results = json_response(conn, 200) + +      [account | _] = results["accounts"] +      assert account["id"] == to_string(user_three.id) + +      assert results["hashtags"] == [] + +      [status] = results["statuses"] +      assert status["id"] == to_string(activity.id) +    end + +    test "search fetches remote statuses", %{conn: conn} do +      capture_log(fn -> +        conn = +          conn +          |> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"}) + +        assert results = json_response(conn, 200) + +        [status] = results["statuses"] + +        assert status["uri"] == +                 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment" +      end) +    end + +    test "search doesn't show statuses that it shouldn't", %{conn: conn} do +      {:ok, activity} = +        CommonAPI.post(insert(:user), %{ +          "status" => "This is about 2hu, but private", +          "visibility" => "private" +        }) + +      capture_log(fn -> +        conn = +          conn +          |> get("/api/v1/search", %{"q" => Object.normalize(activity).data["id"]}) + +        assert results = json_response(conn, 200) + +        [] = results["statuses"] +      end) +    end + +    test "search fetches remote accounts", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> assign(:user, user) +        |> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "true"}) + +      assert results = json_response(conn, 200) +      [account] = results["accounts"] +      assert account["acct"] == "shp@social.heldscal.la" +    end + +    test "search doesn't fetch remote accounts if resolve is false", %{conn: conn} do +      conn = +        conn +        |> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "false"}) + +      assert results = json_response(conn, 200) +      assert [] == results["accounts"] +    end + +    test "search with limit and offset", %{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"}) + +      {:ok, _activity1} = CommonAPI.post(user, %{"status" => "This is about 2hu"}) +      {:ok, _activity2} = CommonAPI.post(user, %{"status" => "This is also about 2hu"}) + +      result = +        conn +        |> get("/api/v1/search", %{"q" => "2hu", "limit" => 1}) + +      assert results = json_response(result, 200) +      assert [%{"id" => activity_id1}] = results["statuses"] +      assert [_] = results["accounts"] + +      results = +        conn +        |> get("/api/v1/search", %{"q" => "2hu", "limit" => 1, "offset" => 1}) +        |> json_response(200) + +      assert [%{"id" => activity_id2}] = results["statuses"] +      assert [] = results["accounts"] + +      assert activity_id1 != activity_id2 +    end + +    test "search returns results only for the given type", %{conn: conn} do +      user = insert(:user) +      _user_two = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"}) + +      {:ok, _activity} = CommonAPI.post(user, %{"status" => "This is about 2hu"}) + +      assert %{"statuses" => [_activity], "accounts" => [], "hashtags" => []} = +               conn +               |> get("/api/v1/search", %{"q" => "2hu", "type" => "statuses"}) +               |> json_response(200) + +      assert %{"statuses" => [], "accounts" => [_user_two], "hashtags" => []} = +               conn +               |> get("/api/v1/search", %{"q" => "2hu", "type" => "accounts"}) +               |> json_response(200) +    end + +    test "search uses account_id to filter statuses by the author", %{conn: conn} do +      user = insert(:user, %{nickname: "shp@shitposter.club"}) +      user_two = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"}) + +      {:ok, activity1} = CommonAPI.post(user, %{"status" => "This is about 2hu"}) +      {:ok, activity2} = CommonAPI.post(user_two, %{"status" => "This is also about 2hu"}) + +      results = +        conn +        |> get("/api/v1/search", %{"q" => "2hu", "account_id" => user.id}) +        |> json_response(200) + +      assert [%{"id" => activity_id1}] = results["statuses"] +      assert activity_id1 == activity1.id +      assert [_] = results["accounts"] + +      results = +        conn +        |> get("/api/v1/search", %{"q" => "2hu", "account_id" => user_two.id}) +        |> json_response(200) + +      assert [%{"id" => activity_id2}] = results["statuses"] +      assert activity_id2 == activity2.id +    end +  end +end diff --git a/test/web/mastodon_api/status_view_test.exs b/test/web/mastodon_api/status_view_test.exs index d7c800e83..1b6beb6d2 100644 --- a/test/web/mastodon_api/status_view_test.exs +++ b/test/web/mastodon_api/status_view_test.exs @@ -23,6 +23,21 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      :ok    end +  test "returns the direct conversation id when given the `with_conversation_id` option" do +    user = insert(:user) + +    {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"}) + +    status = +      StatusView.render("status.json", +        activity: activity, +        with_direct_conversation_id: true, +        for: user +      ) + +    assert status[:pleroma][:direct_conversation_id] +  end +    test "returns a temporary ap_id based user for activities missing db users" do      user = insert(:user) @@ -55,7 +70,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do    test "a note with null content" do      note = insert(:note_activity) -    note_object = Object.normalize(note.data["object"]) +    note_object = Object.normalize(note)      data =        note_object.data @@ -73,26 +88,27 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do    test "a note activity" do      note = insert(:note_activity) +    object_data = Object.normalize(note).data      user = User.get_cached_by_ap_id(note.data["actor"]) -    convo_id = Utils.context_to_conversation_id(note.data["object"]["context"]) +    convo_id = Utils.context_to_conversation_id(object_data["context"])      status = StatusView.render("status.json", %{activity: note})      created_at = -      (note.data["object"]["published"] || "") +      (object_data["published"] || "")        |> String.replace(~r/\.\d+Z/, ".000Z")      expected = %{        id: to_string(note.id), -      uri: note.data["object"]["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}),        in_reply_to_id: nil,        in_reply_to_account_id: nil,        card: nil,        reblog: nil, -      content: HtmlSanitizeEx.basic_html(note.data["object"]["content"]), +      content: HtmlSanitizeEx.basic_html(object_data["content"]),        created_at: created_at,        reblogs_count: 0,        replies_count: 0, @@ -103,14 +119,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do        muted: false,        pinned: false,        sensitive: false, -      spoiler_text: HtmlSanitizeEx.basic_html(note.data["object"]["summary"]), +      poll: nil, +      spoiler_text: HtmlSanitizeEx.basic_html(object_data["summary"]),        visibility: "public",        media_attachments: [],        mentions: [],        tags: [          %{ -          name: "#{note.data["object"]["tag"]}", -          url: "/tag/#{note.data["object"]["tag"]}" +          name: "#{object_data["tag"]}", +          url: "/tag/#{object_data["tag"]}"          }        ],        application: %{ @@ -130,8 +147,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do          local: true,          conversation_id: convo_id,          in_reply_to_account_acct: nil, -        content: %{"text/plain" => HtmlSanitizeEx.strip_tags(note.data["object"]["content"])}, -        spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(note.data["object"]["summary"])} +        content: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["content"])}, +        spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["summary"])}, +        expires_at: nil, +        direct_conversation_id: nil        }      } @@ -201,10 +220,71 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      status = StatusView.render("status.json", %{activity: activity}) -    actor = User.get_cached_by_ap_id(activity.actor) -      assert status.mentions == -             Enum.map([user, actor], fn u -> AccountView.render("mention.json", %{user: u}) end) +             Enum.map([user], fn u -> AccountView.render("mention.json", %{user: u}) end) +  end + +  test "create mentions from the 'to' field" do +    %User{ap_id: recipient_ap_id} = insert(:user) +    cc = insert_pair(:user) |> Enum.map(& &1.ap_id) + +    object = +      insert(:note, %{ +        data: %{ +          "to" => [recipient_ap_id], +          "cc" => cc +        } +      }) + +    activity = +      insert(:note_activity, %{ +        note: object, +        recipients: [recipient_ap_id | cc] +      }) + +    assert length(activity.recipients) == 3 + +    %{mentions: [mention] = mentions} = StatusView.render("status.json", %{activity: activity}) + +    assert length(mentions) == 1 +    assert mention.url == recipient_ap_id +  end + +  test "create mentions from the 'tag' field" do +    recipient = insert(:user) +    cc = insert_pair(:user) |> Enum.map(& &1.ap_id) + +    object = +      insert(:note, %{ +        data: %{ +          "cc" => cc, +          "tag" => [ +            %{ +              "href" => recipient.ap_id, +              "name" => recipient.nickname, +              "type" => "Mention" +            }, +            %{ +              "href" => "https://example.com/search?tag=test", +              "name" => "#test", +              "type" => "Hashtag" +            } +          ] +        } +      }) + +    activity = +      insert(:note_activity, %{ +        note: object, +        recipients: [recipient.ap_id | cc] +      }) + +    assert length(activity.recipients) == 3 + +    %{mentions: [mention] = mentions} = StatusView.render("status.json", %{activity: activity}) + +    assert length(mentions) == 1 +    assert mention.url == recipient.ap_id    end    test "attachments" do @@ -237,6 +317,16 @@ 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("status.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) @@ -341,4 +431,154 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do          StatusView.render("card.json", %{page_url: page_url, rich_media: card})      end    end + +  describe "poll view" do +    test "renders a poll" do +      user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "Is Tenshi eating a corndog cute?", +          "poll" => %{ +            "options" => ["absolutely!", "sure", "yes", "why are you even asking?"], +            "expires_in" => 20 +          } +        }) + +      object = Object.normalize(activity) + +      expected = %{ +        emojis: [], +        expired: false, +        id: to_string(object.id), +        multiple: false, +        options: [ +          %{title: "absolutely!", votes_count: 0}, +          %{title: "sure", votes_count: 0}, +          %{title: "yes", votes_count: 0}, +          %{title: "why are you even asking?", votes_count: 0} +        ], +        voted: false, +        votes_count: 0 +      } + +      result = StatusView.render("poll.json", %{object: object}) +      expires_at = result.expires_at +      result = Map.delete(result, :expires_at) + +      assert result == expected + +      expires_at = NaiveDateTime.from_iso8601!(expires_at) +      assert NaiveDateTime.diff(expires_at, NaiveDateTime.utc_now()) in 15..20 +    end + +    test "detects if it is multiple choice" do +      user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "Which Mastodon developer is your favourite?", +          "poll" => %{ +            "options" => ["Gargron", "Eugen"], +            "expires_in" => 20, +            "multiple" => true +          } +        }) + +      object = Object.normalize(activity) + +      assert %{multiple: true} = StatusView.render("poll.json", %{object: object}) +    end + +    test "detects emoji" do +      user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "What's with the smug face?", +          "poll" => %{ +            "options" => [":blank: sip", ":blank::blank: sip", ":blank::blank::blank: sip"], +            "expires_in" => 20 +          } +        }) + +      object = Object.normalize(activity) + +      assert %{emojis: [%{shortcode: "blank"}]} = +               StatusView.render("poll.json", %{object: object}) +    end + +    test "detects vote status" do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "Which input devices do you use?", +          "poll" => %{ +            "options" => ["mouse", "trackball", "trackpoint"], +            "multiple" => true, +            "expires_in" => 20 +          } +        }) + +      object = Object.normalize(activity) + +      {:ok, _, object} = CommonAPI.vote(other_user, object, [1, 2]) + +      result = StatusView.render("poll.json", %{object: object, for: other_user}) + +      assert result[:voted] == true +      assert Enum.at(result[:options], 1)[:votes_count] == 1 +      assert Enum.at(result[:options], 2)[:votes_count] == 1 +    end +  end + +  test "embeds a relationship in the account" do +    user = insert(:user) +    other_user = insert(:user) + +    {:ok, activity} = +      CommonAPI.post(user, %{ +        "status" => "drink more water" +      }) + +    result = StatusView.render("status.json", %{activity: activity, for: other_user}) + +    assert result[:account][:pleroma][:relationship] == +             AccountView.render("relationship.json", %{user: other_user, target: user}) +  end + +  test "embeds a relationship in the account in reposts" do +    user = insert(:user) +    other_user = insert(:user) + +    {:ok, activity} = +      CommonAPI.post(user, %{ +        "status" => "˙˙ɐʎns" +      }) + +    {:ok, activity, _object} = CommonAPI.repeat(activity.id, other_user) + +    result = StatusView.render("status.json", %{activity: activity, for: user}) + +    assert result[:account][:pleroma][:relationship] == +             AccountView.render("relationship.json", %{user: user, target: other_user}) + +    assert result[:reblog][:account][:pleroma][:relationship] == +             AccountView.render("relationship.json", %{user: user, target: user}) +  end + +  test "visibility/list" do +    user = insert(:user) + +    {:ok, list} = Pleroma.List.create("foo", user) + +    {:ok, activity} = +      CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"}) + +    status = StatusView.render("status.json", activity: activity) + +    assert status.visibility == "list" +  end  end diff --git a/test/web/media_proxy/media_proxy_controller_test.exs b/test/web/media_proxy/media_proxy_controller_test.exs new file mode 100644 index 000000000..53b8f556b --- /dev/null +++ b/test/web/media_proxy/media_proxy_controller_test.exs @@ -0,0 +1,73 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do +  use Pleroma.Web.ConnCase +  import Mock +  alias Pleroma.Config + +  setup do +    media_proxy_config = Config.get([:media_proxy]) || [] +    on_exit(fn -> Config.put([:media_proxy], media_proxy_config) end) +    :ok +  end + +  test "it returns 404 when MediaProxy disabled", %{conn: conn} do +    Config.put([:media_proxy, :enabled], false) + +    assert %Plug.Conn{ +             status: 404, +             resp_body: "Not Found" +           } = get(conn, "/proxy/hhgfh/eeeee") + +    assert %Plug.Conn{ +             status: 404, +             resp_body: "Not Found" +           } = get(conn, "/proxy/hhgfh/eeee/fff") +  end + +  test "it returns 403 when signature invalidated", %{conn: conn} do +    Config.put([:media_proxy, :enabled], true) +    Config.put([Pleroma.Web.Endpoint, :secret_key_base], "00000000000") +    path = URI.parse(Pleroma.Web.MediaProxy.encode_url("https://google.fn")).path +    Config.put([Pleroma.Web.Endpoint, :secret_key_base], "000") + +    assert %Plug.Conn{ +             status: 403, +             resp_body: "Forbidden" +           } = get(conn, path) + +    assert %Plug.Conn{ +             status: 403, +             resp_body: "Forbidden" +           } = get(conn, "/proxy/hhgfh/eeee") + +    assert %Plug.Conn{ +             status: 403, +             resp_body: "Forbidden" +           } = get(conn, "/proxy/hhgfh/eeee/fff") +  end + +  test "redirects on valid url when filename invalidated", %{conn: conn} do +    Config.put([:media_proxy, :enabled], true) +    Config.put([Pleroma.Web.Endpoint, :secret_key_base], "00000000000") +    url = Pleroma.Web.MediaProxy.encode_url("https://google.fn/test.png") +    invalid_url = String.replace(url, "test.png", "test-file.png") +    response = get(conn, invalid_url) +    html = "<html><body>You are being <a href=\"#{url}\">redirected</a>.</body></html>" +    assert response.status == 302 +    assert response.resp_body == html +  end + +  test "it performs ReverseProxy.call when signature valid", %{conn: conn} do +    Config.put([:media_proxy, :enabled], true) +    Config.put([Pleroma.Web.Endpoint, :secret_key_base], "00000000000") +    url = Pleroma.Web.MediaProxy.encode_url("https://google.fn/test.png") + +    with_mock Pleroma.ReverseProxy, +      call: fn _conn, _url, _opts -> %Plug.Conn{status: :success} end do +      assert %Plug.Conn{status: :success} = get(conn, url) +    end +  end +end diff --git a/test/web/media_proxy/media_proxy_test.exs b/test/web/media_proxy/media_proxy_test.exs new file mode 100644 index 000000000..79699cac5 --- /dev/null +++ b/test/web/media_proxy/media_proxy_test.exs @@ -0,0 +1,239 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 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 + +  clear_config([:media_proxy, :enabled]) + +  describe "when enabled" do +    setup do +      Pleroma.Config.put([:media_proxy, :enabled], true) +      :ok +    end + +    test "ignores invalid url" do +      assert url(nil) == nil +      assert url("") == nil +    end + +    test "ignores relative url" do +      assert url("/local") == "/local" +      assert url("/") == "/" +    end + +    test "ignores local url" do +      local_url = Pleroma.Web.Endpoint.url() <> "/hello" +      local_root = Pleroma.Web.Endpoint.url() +      assert url(local_url) == local_url +      assert url(local_root) == local_root +    end + +    test "encodes and decodes URL" do +      url = "https://pleroma.soykaf.com/static/logo.png" +      encoded = url(url) + +      assert String.starts_with?( +               encoded, +               Pleroma.Config.get([:media_proxy, :base_url], Pleroma.Web.base_url()) +             ) + +      assert String.ends_with?(encoded, "/logo.png") + +      assert decode_result(encoded) == url +    end + +    test "encodes and decodes URL without a path" do +      url = "https://pleroma.soykaf.com" +      encoded = url(url) +      assert decode_result(encoded) == url +    end + +    test "encodes and decodes URL without an extension" do +      url = "https://pleroma.soykaf.com/path/" +      encoded = url(url) +      assert String.ends_with?(encoded, "/path") +      assert decode_result(encoded) == url +    end + +    test "encodes and decodes URL and ignores query params for the path" do +      url = "https://pleroma.soykaf.com/static/logo.png?93939393939&bunny=true" +      encoded = url(url) +      assert String.ends_with?(encoded, "/logo.png") +      assert decode_result(encoded) == url +    end + +    test "validates signature" do +      secret_key_base = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base]) + +      on_exit(fn -> +        Pleroma.Config.put([Pleroma.Web.Endpoint, :secret_key_base], secret_key_base) +      end) + +      encoded = url("https://pleroma.social") + +      Pleroma.Config.put( +        [Pleroma.Web.Endpoint, :secret_key_base], +        "00000000000000000000000000000000000000000000000" +      ) + +      [_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/") +      assert decode_url(sig, base64) == {:error, :invalid_signature} +    end + +    test "filename_matches preserves the encoded or decoded path" do +      assert MediaProxyController.filename_matches( +               %{"filename" => "/Hello world.jpg"}, +               "/Hello world.jpg", +               "http://pleroma.social/Hello world.jpg" +             ) == :ok + +      assert MediaProxyController.filename_matches( +               %{"filename" => "/Hello%20world.jpg"}, +               "/Hello%20world.jpg", +               "http://pleroma.social/Hello%20world.jpg" +             ) == :ok + +      assert MediaProxyController.filename_matches( +               %{"filename" => "/my%2Flong%2Furl%2F2019%2F07%2FS.jpg"}, +               "/my%2Flong%2Furl%2F2019%2F07%2FS.jpg", +               "http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg" +             ) == :ok + +      assert MediaProxyController.filename_matches( +               %{"filename" => "/my%2Flong%2Furl%2F2019%2F07%2FS.jp"}, +               "/my%2Flong%2Furl%2F2019%2F07%2FS.jp", +               "http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg" +             ) == {:wrong_filename, "my%2Flong%2Furl%2F2019%2F07%2FS.jpg"} +    end + +    test "encoded url are tried to match for proxy as `conn.request_path` encodes the url" do +      # conn.request_path will return encoded url +      request_path = "/ANALYSE-DAI-_-LE-STABLECOIN-100-D%C3%89CENTRALIS%C3%89-BQ.jpg" + +      assert MediaProxyController.filename_matches( +               true, +               request_path, +               "https://mydomain.com/uploads/2019/07/ANALYSE-DAI-_-LE-STABLECOIN-100-DÉCENTRALISÉ-BQ.jpg" +             ) == :ok +    end + +    test "uses the configured base_url" do +      base_url = Pleroma.Config.get([:media_proxy, :base_url]) + +      if base_url do +        on_exit(fn -> +          Pleroma.Config.put([:media_proxy, :base_url], base_url) +        end) +      end + +      Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social") + +      url = "https://pleroma.soykaf.com/static/logo.png" +      encoded = url(url) + +      assert String.starts_with?(encoded, Pleroma.Config.get([:media_proxy, :base_url])) +    end + +    # Some sites expect ASCII encoded characters in the URL to be preserved even if +    # unnecessary. +    # Issues: https://git.pleroma.social/pleroma/pleroma/issues/580 +    #         https://git.pleroma.social/pleroma/pleroma/issues/1055 +    test "preserve ASCII encoding" do +      url = +        "https://pleroma.com/%20/%21/%22/%23/%24/%25/%26/%27/%28/%29/%2A/%2B/%2C/%2D/%2E/%2F/%30/%31/%32/%33/%34/%35/%36/%37/%38/%39/%3A/%3B/%3C/%3D/%3E/%3F/%40/%41/%42/%43/%44/%45/%46/%47/%48/%49/%4A/%4B/%4C/%4D/%4E/%4F/%50/%51/%52/%53/%54/%55/%56/%57/%58/%59/%5A/%5B/%5C/%5D/%5E/%5F/%60/%61/%62/%63/%64/%65/%66/%67/%68/%69/%6A/%6B/%6C/%6D/%6E/%6F/%70/%71/%72/%73/%74/%75/%76/%77/%78/%79/%7A/%7B/%7C/%7D/%7E/%7F/%80/%81/%82/%83/%84/%85/%86/%87/%88/%89/%8A/%8B/%8C/%8D/%8E/%8F/%90/%91/%92/%93/%94/%95/%96/%97/%98/%99/%9A/%9B/%9C/%9D/%9E/%9F/%C2%A0/%A1/%A2/%A3/%A4/%A5/%A6/%A7/%A8/%A9/%AA/%AB/%AC/%C2%AD/%AE/%AF/%B0/%B1/%B2/%B3/%B4/%B5/%B6/%B7/%B8/%B9/%BA/%BB/%BC/%BD/%BE/%BF/%C0/%C1/%C2/%C3/%C4/%C5/%C6/%C7/%C8/%C9/%CA/%CB/%CC/%CD/%CE/%CF/%D0/%D1/%D2/%D3/%D4/%D5/%D6/%D7/%D8/%D9/%DA/%DB/%DC/%DD/%DE/%DF/%E0/%E1/%E2/%E3/%E4/%E5/%E6/%E7/%E8/%E9/%EA/%EB/%EC/%ED/%EE/%EF/%F0/%F1/%F2/%F3/%F4/%F5/%F6/%F7/%F8/%F9/%FA/%FB/%FC/%FD/%FE/%FF" + +      encoded = url(url) +      assert decode_result(encoded) == url +    end + +    # This includes unsafe/reserved characters which are not interpreted as part of the URL +    # and would otherwise have to be ASCII encoded. It is our role to ensure the proxied URL +    # is unmodified, so we are testing these characters anyway. +    test "preserve non-unicode characters per RFC3986" do +      url = +        "https://pleroma.com/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890-._~:/?#[]@!$&'()*+,;=|^`{}" + +      encoded = url(url) +      assert decode_result(encoded) == url +    end + +    test "preserve unicode characters" do +      url = "https://ko.wikipedia.org/wiki/위키백과:대문" + +      encoded = url(url) +      assert decode_result(encoded) == url +    end +  end + +  describe "when disabled" do +    setup do +      enabled = Pleroma.Config.get([:media_proxy, :enabled]) + +      if enabled do +        Pleroma.Config.put([:media_proxy, :enabled], false) + +        on_exit(fn -> +          Pleroma.Config.put([:media_proxy, :enabled], enabled) +          :ok +        end) +      end + +      :ok +    end + +    test "does not encode remote urls" do +      assert url("https://google.fr") == "https://google.fr" +    end +  end + +  defp decode_result(encoded) do +    [_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/") +    {:ok, decoded} = decode_url(sig, base64) +    decoded +  end + +  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 + +    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/player_view_test.exs b/test/web/metadata/player_view_test.exs new file mode 100644 index 000000000..742b0ed8b --- /dev/null +++ b/test/web/metadata/player_view_test.exs @@ -0,0 +1,33 @@ +# 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.PlayerViewTest do +  use Pleroma.DataCase + +  alias Pleroma.Web.Metadata.PlayerView + +  test "it renders audio tag" do +    res = +      PlayerView.render( +        "player.html", +        %{"mediaType" => "audio", "href" => "test-href"} +      ) +      |> Phoenix.HTML.safe_to_string() + +    assert res == +             "<audio controls><source src=\"test-href\" type=\"audio\">Your browser does not support audio playback.</audio>" +  end + +  test "it renders videos tag" do +    res = +      PlayerView.render( +        "player.html", +        %{"mediaType" => "video", "href" => "test-href"} +      ) +      |> Phoenix.HTML.safe_to_string() + +    assert res == +             "<video controls loop><source src=\"test-href\" type=\"video\">Your browser does not support video playback.</video>" +  end +end diff --git a/test/web/metadata/rel_me_test.exs b/test/web/metadata/rel_me_test.exs index f66bf7834..3874e077b 100644 --- a/test/web/metadata/rel_me_test.exs +++ b/test/web/metadata/rel_me_test.exs @@ -1,3 +1,7 @@ +# 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.RelMeTest do    use Pleroma.DataCase    import Pleroma.Factory diff --git a/test/web/metadata/twitter_card_test.exs b/test/web/metadata/twitter_card_test.exs new file mode 100644 index 000000000..0814006d2 --- /dev/null +++ b/test/web/metadata/twitter_card_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.Metadata.Providers.TwitterCardTest do +  use Pleroma.DataCase +  import Pleroma.Factory + +  alias Pleroma.User +  alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.Endpoint +  alias Pleroma.Web.Metadata.Providers.TwitterCard +  alias Pleroma.Web.Metadata.Utils +  alias Pleroma.Web.Router + +  test "it renders twitter card for user info" do +    user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994") +    avatar_url = Utils.attachment_url(User.avatar_url(user)) +    res = TwitterCard.build_tags(%{user: user}) + +    assert res == [ +             {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []}, +             {:meta, [property: "twitter:description", content: "born 19 March 1994"], []}, +             {:meta, [property: "twitter:image", content: avatar_url], []}, +             {:meta, [property: "twitter:card", content: "summary"], []} +           ] +  end + +  test "it does not render attachments if post is nsfw" 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"}) + +    note = +      insert(:note, %{ +        data: %{ +          "actor" => user.ap_id, +          "tag" => [], +          "id" => "https://pleroma.gov/objects/whatever", +          "content" => "pleroma in a nutshell", +          "sensitive" => true, +          "attachment" => [ +            %{ +              "url" => [%{"mediaType" => "image/png", "href" => "https://pleroma.gov/tenshi.png"}] +            }, +            %{ +              "url" => [ +                %{ +                  "mediaType" => "application/octet-stream", +                  "href" => "https://pleroma.gov/fqa/badapple.sfc" +                } +              ] +            }, +            %{ +              "url" => [ +                %{"mediaType" => "video/webm", "href" => "https://pleroma.gov/about/juche.webm"} +              ] +            } +          ] +        } +      }) + +    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_large_image"], []} +           ] == result +  end + +  test "it renders supported types of attachments and skips unknown types" 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", +          "attachment" => [ +            %{ +              "url" => [%{"mediaType" => "image/png", "href" => "https://pleroma.gov/tenshi.png"}] +            }, +            %{ +              "url" => [ +                %{ +                  "mediaType" => "application/octet-stream", +                  "href" => "https://pleroma.gov/fqa/badapple.sfc" +                } +              ] +            }, +            %{ +              "url" => [ +                %{"mediaType" => "video/webm", "href" => "https://pleroma.gov/about/juche.webm"} +              ] +            } +          ] +        } +      }) + +    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:card", content: "summary_large_image"], []}, +             {:meta, [property: "twitter:player", content: "https://pleroma.gov/tenshi.png"], []}, +             {:meta, [property: "twitter:card", content: "player"], []}, +             {:meta, +              [ +                property: "twitter:player", +                content: Router.Helpers.o_status_url(Endpoint, :notice_player, activity.id) +              ], []}, +             {:meta, [property: "twitter:player:width", content: "480"], []}, +             {:meta, [property: "twitter:player:height", content: "480"], []} +           ] == result +  end +end diff --git a/test/web/node_info_test.exs b/test/web/node_info_test.exs index be1173513..f6147c286 100644 --- a/test/web/node_info_test.exs +++ b/test/web/node_info_test.exs @@ -83,4 +83,55 @@ defmodule Pleroma.Web.NodeInfoTest do      Pleroma.Config.put([:instance, :safe_dm_mentions], option)    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) + +    simple_config = %{"reject" => ["example.com"]} +    Pleroma.Config.put(:mrf_simple, simple_config) + +    response = +      conn +      |> get("/nodeinfo/2.1.json") +      |> json_response(:ok) + +    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) + +    exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions]) +    Pleroma.Config.put([:instance, :mrf_transparency_exclusions], ["other.site"]) + +    simple_config = %{"reject" => ["example.com", "other.site"]} +    expected_config = %{"reject" => ["example.com"]} + +    Pleroma.Config.put(:mrf_simple, simple_config) + +    response = +      conn +      |> get("/nodeinfo/2.1.json") +      |> json_response(:ok) + +    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, %{}) +  end  end 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 1c04ac9ad..b492c7794 100644 --- a/test/web/oauth/oauth_controller_test.exs +++ b/test/web/oauth/oauth_controller_test.exs @@ -5,30 +5,21 @@  defmodule Pleroma.Web.OAuth.OAuthControllerTest do    use Pleroma.Web.ConnCase    import Pleroma.Factory -  import Mock -  alias Pleroma.Registration    alias Pleroma.Repo    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: @@ -38,6 +29,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 @@ -49,7 +47,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do            %{              "response_type" => "code",              "client_id" => app.client_id, -            "redirect_uri" => app.redirect_uris, +            "redirect_uri" => OAuthController.default_redirect_uri(app),              "scope" => "read"            }          ) @@ -72,7 +70,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do              "authorization" => %{                "scope" => "read follow",                "client_id" => app.client_id, -              "redirect_uri" => app.redirect_uris, +              "redirect_uri" => OAuthController.default_redirect_uri(app),                "state" => "a_state"              }            } @@ -98,64 +96,65 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do      test "with user-bound registration, GET /oauth/<provider>/callback redirects to `redirect_uri` with `code`",           %{app: app, conn: conn} do        registration = insert(:registration) +      redirect_uri = OAuthController.default_redirect_uri(app)        state_params = %{          "scope" => Enum.join(app.scopes, " "),          "client_id" => app.client_id, -        "redirect_uri" => app.redirect_uris, +        "redirect_uri" => redirect_uri,          "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/#{app.redirect_uris}\?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",          "client_id" => app.client_id, -        "redirect_uri" => app.redirect_uris, +        "redirect_uri" => OAuthController.default_redirect_uri(app),          "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`", %{ @@ -165,7 +164,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do        state_params = %{          "scope" => Enum.join(app.scopes, " "),          "client_id" => app.client_id, -        "redirect_uri" => app.redirect_uris, +        "redirect_uri" => OAuthController.default_redirect_uri(app),          "state" => ""        } @@ -199,7 +198,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do              "authorization" => %{                "scopes" => app.scopes,                "client_id" => app.client_id, -              "redirect_uri" => app.redirect_uris, +              "redirect_uri" => OAuthController.default_redirect_uri(app),                "state" => "a_state",                "nickname" => nil,                "email" => "john@doe.com" @@ -218,6 +217,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do             conn: conn           } do        registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil}) +      redirect_uri = OAuthController.default_redirect_uri(app)        conn =          conn @@ -229,7 +229,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do              "authorization" => %{                "scopes" => app.scopes,                "client_id" => app.client_id, -              "redirect_uri" => app.redirect_uris, +              "redirect_uri" => redirect_uri,                "state" => "a_state",                "nickname" => "availablenick",                "email" => "available@email.com" @@ -238,7 +238,36 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do          )        assert response = html_response(conn, 302) -      assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/ +      assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/ +    end + +    test "with unlisted `redirect_uri`, POST /oauth/register?op=register results in HTTP 401", +         %{ +           app: app, +           conn: conn +         } do +      registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil}) +      unlisted_redirect_uri = "http://cross-site-request.com" + +      conn = +        conn +        |> put_session(:registration_id, registration.id) +        |> post( +          "/oauth/register", +          %{ +            "op" => "register", +            "authorization" => %{ +              "scopes" => app.scopes, +              "client_id" => app.client_id, +              "redirect_uri" => unlisted_redirect_uri, +              "state" => "a_state", +              "nickname" => "availablenick", +              "email" => "available@email.com" +            } +          } +        ) + +      assert response = html_response(conn, 401)      end      test "with invalid params, POST /oauth/register?op=register renders registration_details page", @@ -254,7 +283,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do          "authorization" => %{            "scopes" => app.scopes,            "client_id" => app.client_id, -          "redirect_uri" => app.redirect_uris, +          "redirect_uri" => OAuthController.default_redirect_uri(app),            "state" => "a_state",            "nickname" => "availablenickname",            "email" => "available@email.com" @@ -286,6 +315,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do           } do        user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("testpassword"))        registration = insert(:registration, user: nil) +      redirect_uri = OAuthController.default_redirect_uri(app)        conn =          conn @@ -297,7 +327,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do              "authorization" => %{                "scopes" => app.scopes,                "client_id" => app.client_id, -              "redirect_uri" => app.redirect_uris, +              "redirect_uri" => redirect_uri,                "state" => "a_state",                "name" => user.nickname,                "password" => "testpassword" @@ -306,7 +336,37 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do          )        assert response = html_response(conn, 302) -      assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/ +      assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/ +    end + +    test "with unlisted `redirect_uri`, POST /oauth/register?op=connect results in HTTP 401`", +         %{ +           app: app, +           conn: conn +         } do +      user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("testpassword")) +      registration = insert(:registration, user: nil) +      unlisted_redirect_uri = "http://cross-site-request.com" + +      conn = +        conn +        |> put_session(:registration_id, registration.id) +        |> post( +          "/oauth/register", +          %{ +            "op" => "connect", +            "authorization" => %{ +              "scopes" => app.scopes, +              "client_id" => app.client_id, +              "redirect_uri" => unlisted_redirect_uri, +              "state" => "a_state", +              "name" => user.nickname, +              "password" => "testpassword" +            } +          } +        ) + +      assert response = html_response(conn, 401)      end      test "with invalid params, POST /oauth/register?op=connect renders registration_details page", @@ -322,7 +382,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do          "authorization" => %{            "scopes" => app.scopes,            "client_id" => app.client_id, -          "redirect_uri" => app.redirect_uris, +          "redirect_uri" => OAuthController.default_redirect_uri(app),            "state" => "a_state",            "name" => user.nickname,            "password" => "wrong password" @@ -358,7 +418,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do            %{              "response_type" => "code",              "client_id" => app.client_id, -            "redirect_uri" => app.redirect_uris, +            "redirect_uri" => OAuthController.default_redirect_uri(app),              "scope" => "read"            }          ) @@ -378,7 +438,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do              "authorization" => %{                "response_type" => "code",                "client_id" => app.client_id, -              "redirect_uri" => app.redirect_uris, +              "redirect_uri" => OAuthController.default_redirect_uri(app),                "scope" => "read"              }            } @@ -399,7 +459,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do            %{              "response_type" => "code",              "client_id" => app.client_id, -            "redirect_uri" => app.redirect_uris, +            "redirect_uri" => OAuthController.default_redirect_uri(app),              "scope" => "read",              "force_login" => "true"            } @@ -408,7 +468,11 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do        assert html_response(conn, 200) =~ ~s(type="submit")      end -    test "redirects to app if user is already authenticated", %{app: app, conn: conn} do +    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)        conn = @@ -419,12 +483,62 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do            %{              "response_type" => "code",              "client_id" => app.client_id, -            "redirect_uri" => app.redirect_uris, +            "redirect_uri" => OAuthController.default_redirect_uri(app), +            "state" => "specific_client_state",              "scope" => "read"            }          ) -      assert redirected_to(conn) == "https://redirect.url" +      assert URI.decode(redirected_to(conn)) == +               "https://redirect.url?access_token=#{token.token}&state=specific_client_state" +    end + +    test "with existing authentication and unlisted non-OOB `redirect_uri`, redirects without credentials", +         %{ +           app: app, +           conn: conn +         } do +      unlisted_redirect_uri = "http://cross-site-request.com" +      token = insert(:oauth_token, app_id: app.id) + +      conn = +        conn +        |> put_session(:oauth_token, token.token) +        |> get( +          "/oauth/authorize", +          %{ +            "response_type" => "code", +            "client_id" => app.client_id, +            "redirect_uri" => unlisted_redirect_uri, +            "state" => "specific_client_state", +            "scope" => "read" +          } +        ) + +      assert redirected_to(conn) == unlisted_redirect_uri +    end + +    test "with existing authentication and OOB `redirect_uri`, redirects to app with `token` and `state` params", +         %{ +           app: app, +           conn: conn +         } do +      token = insert(:oauth_token, app_id: app.id) + +      conn = +        conn +        |> put_session(:oauth_token, token.token) +        |> get( +          "/oauth/authorize", +          %{ +            "response_type" => "code", +            "client_id" => app.client_id, +            "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob", +            "scope" => "read" +          } +        ) + +      assert html_response(conn, 200) =~ "Authorization exists"      end    end @@ -432,6 +546,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do      test "redirects with oauth authorization" do        user = insert(:user)        app = insert(:oauth_app, scopes: ["read", "write", "follow"]) +      redirect_uri = OAuthController.default_redirect_uri(app)        conn =          build_conn() @@ -440,14 +555,14 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do              "name" => user.nickname,              "password" => "test",              "client_id" => app.client_id, -            "redirect_uri" => app.redirect_uris, +            "redirect_uri" => redirect_uri,              "scope" => "read write",              "state" => "statepassed"            }          })        target = redirected_to(conn) -      assert target =~ app.redirect_uris +      assert target =~ redirect_uri        query = URI.parse(target).query |> URI.query_decoder() |> Map.new() @@ -460,6 +575,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do      test "returns 401 for wrong credentials", %{conn: conn} do        user = insert(:user)        app = insert(:oauth_app) +      redirect_uri = OAuthController.default_redirect_uri(app)        result =          conn @@ -468,7 +584,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do              "name" => user.nickname,              "password" => "wrong",              "client_id" => app.client_id, -            "redirect_uri" => app.redirect_uris, +            "redirect_uri" => redirect_uri,              "state" => "statepassed",              "scope" => Enum.join(app.scopes, " ")            } @@ -477,7 +593,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do        # Keep the details        assert result =~ app.client_id -      assert result =~ app.redirect_uris +      assert result =~ redirect_uri        # Error message        assert result =~ "Invalid Username/Password" @@ -486,6 +602,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do      test "returns 401 for missing scopes", %{conn: conn} do        user = insert(:user)        app = insert(:oauth_app) +      redirect_uri = OAuthController.default_redirect_uri(app)        result =          conn @@ -494,7 +611,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do              "name" => user.nickname,              "password" => "test",              "client_id" => app.client_id, -            "redirect_uri" => app.redirect_uris, +            "redirect_uri" => redirect_uri,              "state" => "statepassed",              "scope" => ""            } @@ -503,7 +620,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do        # Keep the details        assert result =~ app.client_id -      assert result =~ app.redirect_uris +      assert result =~ redirect_uri        # Error message        assert result =~ "This action is outside the authorized scopes" @@ -512,6 +629,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do      test "returns 401 for scopes beyond app scopes", %{conn: conn} do        user = insert(:user)        app = insert(:oauth_app, scopes: ["read", "write"]) +      redirect_uri = OAuthController.default_redirect_uri(app)        result =          conn @@ -520,7 +638,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do              "name" => user.nickname,              "password" => "test",              "client_id" => app.client_id, -            "redirect_uri" => app.redirect_uris, +            "redirect_uri" => redirect_uri,              "state" => "statepassed",              "scope" => "read write follow"            } @@ -529,7 +647,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do        # Keep the details        assert result =~ app.client_id -      assert result =~ app.redirect_uris +      assert result =~ redirect_uri        # Error message        assert result =~ "This action is outside the authorized scopes" @@ -548,7 +666,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do          |> post("/oauth/token", %{            "grant_type" => "authorization_code",            "code" => auth.token, -          "redirect_uri" => app.redirect_uris, +          "redirect_uri" => OAuthController.default_redirect_uri(app),            "client_id" => app.client_id,            "client_secret" => app.client_secret          }) @@ -602,7 +720,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do          |> post("/oauth/token", %{            "grant_type" => "authorization_code",            "code" => auth.token, -          "redirect_uri" => app.redirect_uris +          "redirect_uri" => OAuthController.default_redirect_uri(app)          })        assert %{"access_token" => token, "scope" => scope} = json_response(conn, 200) @@ -647,7 +765,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do          |> post("/oauth/token", %{            "grant_type" => "authorization_code",            "code" => auth.token, -          "redirect_uri" => app.redirect_uris +          "redirect_uri" => OAuthController.default_redirect_uri(app)          })        assert resp = json_response(conn, 400) @@ -656,12 +774,7 @@ 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)) @@ -726,7 +839,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do          |> post("/oauth/token", %{            "grant_type" => "authorization_code",            "code" => "Imobviouslyinvalid", -          "redirect_uri" => app.redirect_uris, +          "redirect_uri" => OAuthController.default_redirect_uri(app),            "client_id" => app.client_id,            "client_secret" => app.client_secret          }) @@ -738,16 +851,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"]) @@ -787,7 +894,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/ostatus/activity_representer_test.exs b/test/web/ostatus/activity_representer_test.exs index 16ee02abb..a3a92ce5b 100644 --- a/test/web/ostatus/activity_representer_test.exs +++ b/test/web/ostatus/activity_representer_test.exs @@ -38,22 +38,23 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest do    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>#{note_activity.data["object"]["id"]}</id> +    <id>#{object_data["id"]}</id>      <title>New note by #{user.nickname}</title> -    <content type="html">#{note_activity.data["object"]["content"]}</content> -    <published>#{note_activity.data["object"]["published"]}</published> -    <updated>#{note_activity.data["object"]["published"]}</updated> +    <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>#{note_activity.data["object"]["summary"]}</summary> -    <link type="application/atom+xml" href="#{note_activity.data["object"]["id"]}" rel="self" /> -    <link type="text/html" href="#{note_activity.data["object"]["id"]}" rel="alternate" /> +    <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" /> @@ -106,7 +107,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest do    test "an announce activity" do      note = insert(:note_activity)      user = insert(:user) -    object = Object.get_cached_by_ap_id(note.data["object"]["id"]) +    object = Object.normalize(note)      {:ok, announce, _object} = ActivityPub.announce(user, object) @@ -125,7 +126,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest do      <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 #{note.data["object"]["content"]}</content> +    <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> diff --git a/test/web/ostatus/incoming_documents/delete_handling_test.exs b/test/web/ostatus/incoming_documents/delete_handling_test.exs index ca6e61339..cd0447af7 100644 --- a/test/web/ostatus/incoming_documents/delete_handling_test.exs +++ b/test/web/ostatus/incoming_documents/delete_handling_test.exs @@ -1,3 +1,7 @@ +# 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 @@ -17,8 +21,9 @@ defmodule Pleroma.Web.OStatus.DeleteHandlingTest 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) -      object = Object.get_by_ap_id(note.data["object"]["id"])        {:ok, like, _object} = Pleroma.Web.ActivityPub.ActivityPub.like(user, object) @@ -26,16 +31,16 @@ defmodule Pleroma.Web.OStatus.DeleteHandlingTest do          File.read!("test/fixtures/delete.xml")          |> String.replace(            "tag:mastodon.sdf.org,2017-06-10:objectId=310513:objectType=Status", -          note.data["object"]["id"] +          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(note.data["object"]["id"]).data["type"] == "Tombstone" +      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_note.data["object"]["id"]) +      assert Object.get_by_ap_id(second_object.data["id"])        assert delete.data["type"] == "Delete"      end diff --git a/test/web/ostatus/ostatus_controller_test.exs b/test/web/ostatus/ostatus_controller_test.exs index 7441e5fce..095ae7041 100644 --- a/test/web/ostatus/ostatus_controller_test.exs +++ b/test/web/ostatus/ostatus_controller_test.exs @@ -4,7 +4,10 @@  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 @@ -15,29 +18,37 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do      :ok    end +  clear_config_all([:instance, :federating]) do +    Pleroma.Config.put([:instance, :federating], true) +  end +    describe "salmon_incoming" do      test "decodes a salmon", %{conn: conn} do        user = insert(:user)        salmon = File.read!("test/fixtures/salmon.xml") -      conn = -        conn -        |> put_req_header("content-type", "application/atom+xml") -        |> post("/users/#{user.nickname}/salmon", salmon) +      assert capture_log(fn -> +               conn = +                 conn +                 |> put_req_header("content-type", "application/atom+xml") +                 |> post("/users/#{user.nickname}/salmon", salmon) -      assert response(conn, 200) +               assert response(conn, 200) +             end) =~ "[error]"      end      test "decodes a salmon with a changed magic key", %{conn: conn} do        user = insert(:user)        salmon = File.read!("test/fixtures/salmon.xml") -      conn = -        conn -        |> put_req_header("content-type", "application/atom+xml") -        |> post("/users/#{user.nickname}/salmon", salmon) +      assert capture_log(fn -> +               conn = +                 conn +                 |> put_req_header("content-type", "application/atom+xml") +                 |> post("/users/#{user.nickname}/salmon", salmon) -      assert response(conn, 200) +               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") @@ -54,17 +65,20 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do        |> Ecto.Changeset.put_embed(:info, info_cng)        |> User.update_and_set_cache() -      conn = -        build_conn() -        |> put_req_header("content-type", "application/atom+xml") -        |> post("/users/#{user.nickname}/salmon", salmon) +      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) +               assert response(conn, 200) +             end) =~ "[error]"      end    end    test "gets a feed", %{conn: conn} do      note_activity = insert(:note_activity) +    object = Object.normalize(note_activity)      user = User.get_cached_by_ap_id(note_activity.data["actor"])      conn = @@ -72,7 +86,7 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do        |> put_req_header("content-type", "application/atom+xml")        |> get("/users/#{user.nickname}/feed.atom") -    assert response(conn, 200) =~ note_activity.data["object"]["content"] +    assert response(conn, 200) =~ object.data["content"]    end    test "returns 404 for a missing feed", %{conn: conn} do @@ -84,158 +98,538 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do      assert response(conn, 404)    end -  test "gets an object", %{conn: conn} do -    note_activity = insert(:note_activity) -    user = User.get_cached_by_ap_id(note_activity.data["actor"]) -    [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["object"]["id"])) -    url = "/objects/#{uuid}" +  describe "GET object/2" do +    test "gets an object", %{conn: conn} do +      note_activity = insert(:note_activity) +      object = Object.normalize(note_activity) +      user = User.get_cached_by_ap_id(note_activity.data["actor"]) +      [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) +      url = "/objects/#{uuid}" -    conn = -      conn -      |> put_req_header("accept", "application/xml") -      |> get(url) +      conn = +        conn +        |> put_req_header("accept", "application/xml") +        |> get(url) -    expected = -      ActivityRepresenter.to_simple_form(note_activity, user, true) -      |> ActivityRepresenter.wrap_with_entry() -      |> :xmerl.export_simple(:xmerl_xml) -      |> to_string +      expected = +        ActivityRepresenter.to_simple_form(note_activity, user, true) +        |> ActivityRepresenter.wrap_with_entry() +        |> :xmerl.export_simple(:xmerl_xml) +        |> to_string -    assert response(conn, 200) == expected -  end +      assert response(conn, 200) == expected +    end -  test "404s on private objects", %{conn: conn} do -    note_activity = insert(:direct_note_activity) -    [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["object"]["id"])) +    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}" -    conn -    |> get("/objects/#{uuid}") -    |> response(404) -  end +      conn = +        conn +        |> put_req_header("accept", "text/html") +        |> get(url) -  test "404s on nonexisting objects", %{conn: conn} do -    conn -    |> get("/objects/123") -    |> response(404) -  end +      assert redirected_to(conn) == "/notice/#{note_activity.id}" +    end -  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"])) +    test "500s when user not found", %{conn: conn} do +      note_activity = insert(:note_activity) +      object = Object.normalize(note_activity) +      user = User.get_cached_by_ap_id(note_activity.data["actor"]) +      User.invalidate_cache(user) +      Pleroma.Repo.delete(user) +      [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) +      url = "/objects/#{uuid}" -    conn -    |> put_req_header("accept", "application/xml") -    |> get("/activities/#{uuid}") -    |> response(200) -  end +      conn = +        conn +        |> put_req_header("accept", "application/xml") +        |> get(url) -  test "404s on deleted objects", %{conn: conn} do -    note_activity = insert(:note_activity) -    [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["object"]["id"])) -    object = Object.get_by_ap_id(note_activity.data["object"]["id"]) +      assert response(conn, 500) == ~S({"error":"Something went wrong"}) +    end -    conn -    |> put_req_header("accept", "application/xml") -    |> get("/objects/#{uuid}") -    |> response(200) +    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"])) -    Object.delete(object) +      conn +      |> get("/objects/#{uuid}") +      |> response(404) +    end -    conn -    |> put_req_header("accept", "application/xml") -    |> get("/objects/#{uuid}") -    |> response(404) +    test "404s on nonexisting objects", %{conn: conn} do +      conn +      |> get("/objects/123") +      |> response(404) +    end    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"])) +  describe "GET activity/2" do +    test "gets an activity in xml format", %{conn: conn} do +      note_activity = insert(:note_activity) +      [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) -    conn -    |> get("/activities/#{uuid}") -    |> response(404) -  end +      conn +      |> put_req_header("accept", "application/xml") +      |> get("/activities/#{uuid}") +      |> response(200) +    end -  test "404s on nonexistent activities", %{conn: conn} do -    conn -    |> get("/activities/123") -    |> response(404) -  end +    test "redirects to /notice/id for html format", %{conn: conn} do +      note_activity = insert(:note_activity) +      [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) -  test "gets a notice in xml format", %{conn: conn} do -    note_activity = insert(:note_activity) +      conn = +        conn +        |> put_req_header("accept", "text/html") +        |> get("/activities/#{uuid}") -    conn -    |> get("/notice/#{note_activity.id}") -    |> response(200) -  end +      assert redirected_to(conn) == "/notice/#{note_activity.id}" +    end -  test "gets a notice in AS2 format", %{conn: conn} do -    note_activity = insert(:note_activity) +    test "505s when user not found", %{conn: conn} do +      note_activity = insert(:note_activity) +      [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) +      user = User.get_cached_by_ap_id(note_activity.data["actor"]) +      User.invalidate_cache(user) +      Pleroma.Repo.delete(user) -    conn -    |> put_req_header("accept", "application/activity+json") -    |> get("/notice/#{note_activity.id}") -    |> json_response(200) -  end +      conn = +        conn +        |> put_req_header("accept", "text/html") +        |> get("/activities/#{uuid}") -  test "only gets a notice in AS2 format for Create messages", %{conn: conn} do -    note_activity = insert(:note_activity) -    url = "/notice/#{note_activity.id}" +      assert response(conn, 500) == ~S({"error":"Something went wrong"}) +    end + +    test "404s on deleted objects", %{conn: conn} do +      note_activity = insert(:note_activity) +      object = Object.normalize(note_activity) +      [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) -    conn =        conn -      |> put_req_header("accept", "application/activity+json") -      |> get(url) +      |> put_req_header("accept", "application/xml") +      |> get("/objects/#{uuid}") +      |> response(200) -    assert json_response(conn, 200) +      Object.delete(object) -    user = insert(:user) +      conn +      |> put_req_header("accept", "application/xml") +      |> get("/objects/#{uuid}") +      |> response(404) +    end -    {:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user) -    url = "/notice/#{like_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 like_activity.data["type"] == "Like" +      conn +      |> get("/activities/#{uuid}") +      |> response(404) +    end -    conn = -      build_conn() -      |> put_req_header("accept", "application/activity+json") -      |> get(url) +    test "404s on nonexistent activities", %{conn: conn} do +      conn +      |> get("/activities/123") +      |> response(404) +    end -    assert response(conn, 404) +    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}" + +      conn = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get(url) + +      assert json_response(conn, 200) +    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/2" do +    test "gets a notice in xml format", %{conn: conn} do +      note_activity = insert(:note_activity) + +      conn +      |> get("/notice/#{note_activity.id}") +      |> response(200) +    end + +    test "gets a notice in AS2 format", %{conn: conn} do +      note_activity = insert(:note_activity) -    conn =        conn        |> put_req_header("accept", "application/activity+json") -      |> get(url) +      |> get("/notice/#{note_activity.id}") +      |> json_response(200) +    end + +    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 = +        conn +        |> get("/notice/#{note_activity.id}") + +      assert response(conn, 500) == ~S({"error":"Something went wrong"}) +    end + +    test "only gets a notice in AS2 format for Create messages", %{conn: conn} do +      note_activity = insert(:note_activity) +      url = "/notice/#{note_activity.id}" + +      conn = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get(url) -    assert json_response(conn, 200) +      assert json_response(conn, 200) + +      user = insert(:user) + +      {:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user) +      url = "/notice/#{like_activity.id}" + +      assert like_activity.data["type"] == "Like" + +      conn = +        build_conn() +        |> put_req_header("accept", "application/activity+json") +        |> get(url) + +      assert response(conn, 404) +    end + +    test "render html for redirect for html format", %{conn: conn} do +      note_activity = insert(:note_activity) + +      resp = +        conn +        |> put_req_header("accept", "text/html") +        |> get("/notice/#{note_activity.id}") +        |> response(200) + +      assert resp =~ +               "<meta content=\"#{Pleroma.Web.base_url()}/notice/#{note_activity.id}\" property=\"og:url\">" + +      user = insert(:user) + +      {:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user) + +      assert like_activity.data["type"] == "Like" + +      resp = +        conn +        |> put_req_header("accept", "text/html") +        |> get("/notice/#{like_activity.id}") +        |> response(200) + +      assert resp =~ "<!--server-generated-meta-->" +    end + +    test "404s a private notice", %{conn: conn} do +      note_activity = insert(:direct_note_activity) +      url = "/notice/#{note_activity.id}" + +      conn = +        conn +        |> get(url) + +      assert response(conn, 404) +    end + +    test "404s a nonexisting notice", %{conn: conn} do +      url = "/notice/123" + +      conn = +        conn +        |> get(url) + +      assert response(conn, 404) +    end    end -  test "404s a private notice", %{conn: conn} do -    note_activity = insert(:direct_note_activity) -    url = "/notice/#{note_activity.id}" +  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"]) -    conn = -      conn -      |> get(url) +      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 -    assert response(conn, 404) +    test "undefined format. it returns error when user not found", %{conn: conn} do +      response = +        conn +        |> put_req_header("accept", "application/xml") +        |> get("/users/jimm") +        |> response(404) + +      assert response == ~S({"error":"Not found"}) +    end + +    test "activity+json format. it redirects on actual feed of user", %{conn: conn} do +      note_activity = insert(:note_activity) +      user = User.get_cached_by_ap_id(note_activity.data["actor"]) + +      response = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/users/#{user.nickname}") +        |> json_response(200) + +      assert response["endpoints"] == %{ +               "oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize", +               "oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps", +               "oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token", +               "sharedInbox" => "#{Pleroma.Web.base_url()}/inbox" +             } + +      assert response["@context"] == [ +               "https://www.w3.org/ns/activitystreams", +               "http://localhost:4001/schemas/litepub-0.1.jsonld", +               %{"@language" => "und"} +             ] + +      assert Map.take(response, [ +               "followers", +               "following", +               "id", +               "inbox", +               "manuallyApprovesFollowers", +               "name", +               "outbox", +               "preferredUsername", +               "summary", +               "tag", +               "type", +               "url" +             ]) == %{ +               "followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers", +               "following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following", +               "id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}", +               "inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox", +               "manuallyApprovesFollowers" => false, +               "name" => user.name, +               "outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox", +               "preferredUsername" => user.nickname, +               "summary" => user.bio, +               "tag" => [], +               "type" => "Person", +               "url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}" +             } +    end + +    test "activity+json format. it returns error whe use not found", %{conn: conn} do +      response = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/users/jimm") +        |> json_response(404) + +      assert response == "Not found" +    end + +    test "json format. it redirects on actual feed of user", %{conn: conn} do +      note_activity = insert(:note_activity) +      user = User.get_cached_by_ap_id(note_activity.data["actor"]) + +      response = +        conn +        |> put_req_header("accept", "application/json") +        |> get("/users/#{user.nickname}") +        |> json_response(200) + +      assert response["endpoints"] == %{ +               "oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize", +               "oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps", +               "oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token", +               "sharedInbox" => "#{Pleroma.Web.base_url()}/inbox" +             } + +      assert response["@context"] == [ +               "https://www.w3.org/ns/activitystreams", +               "http://localhost:4001/schemas/litepub-0.1.jsonld", +               %{"@language" => "und"} +             ] + +      assert Map.take(response, [ +               "followers", +               "following", +               "id", +               "inbox", +               "manuallyApprovesFollowers", +               "name", +               "outbox", +               "preferredUsername", +               "summary", +               "tag", +               "type", +               "url" +             ]) == %{ +               "followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers", +               "following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following", +               "id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}", +               "inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox", +               "manuallyApprovesFollowers" => false, +               "name" => user.name, +               "outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox", +               "preferredUsername" => user.nickname, +               "summary" => user.bio, +               "tag" => [], +               "type" => "Person", +               "url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}" +             } +    end + +    test "json format. it returns error whe use not found", %{conn: conn} do +      response = +        conn +        |> put_req_header("accept", "application/json") +        |> get("/users/jimm") +        |> json_response(404) + +      assert response == "Not found" +    end + +    test "html format. it redirects on actual feed of user", %{conn: conn} do +      note_activity = insert(:note_activity) +      user = User.get_cached_by_ap_id(note_activity.data["actor"]) + +      response = +        conn +        |> get("/users/#{user.nickname}") +        |> response(200) + +      assert response == +               Fallback.RedirectController.redirector_with_meta( +                 conn, +                 %{user: user} +               ).resp_body +    end + +    test "html format. it returns error when user not found", %{conn: conn} do +      response = +        conn +        |> get("/users/jimm") +        |> json_response(404) + +      assert response == %{"error" => "Not found"} +    end    end -  test "404s a nonexisting notice", %{conn: conn} do -    url = "/notice/123" +  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(url) +      conn = +        conn +        |> get("/notice/#{note_activity.id}/embed_player") -    assert response(conn, 404) +      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 + +    test "404s when activity isn't create", %{conn: conn} do +      note_activity = insert(:note_activity, data_attrs: %{"type" => "Like"}) + +      assert conn +             |> get("/notice/#{note_activity.id}/embed_player") +             |> response(404) +    end + +    test "404s when activity is direct message", %{conn: conn} do +      note_activity = insert(:note_activity, data_attrs: %{"directMessage" => true}) + +      assert conn +             |> get("/notice/#{note_activity.id}/embed_player") +             |> response(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", []) + +      object +      |> Ecto.Changeset.change(data: object_data) +      |> Pleroma.Repo.update() + +      assert conn +             |> get("/notice/#{note_activity.id}/embed_player") +             |> response(404) +    end + +    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 index f6be16862..803a97695 100644 --- a/test/web/ostatus/ostatus_test.exs +++ b/test/web/ostatus/ostatus_test.exs @@ -11,8 +11,10 @@ defmodule Pleroma.Web.OStatusTest do    alias Pleroma.User    alias Pleroma.Web.OStatus    alias Pleroma.Web.XML -  import Pleroma.Factory +    import ExUnit.CaptureLog +  import Mock +  import Pleroma.Factory    setup_all do      Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -28,7 +30,7 @@ defmodule Pleroma.Web.OStatusTest do    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.data["object"]) +    object = Object.normalize(activity)      user = User.get_cached_by_ap_id(activity.data["actor"])      assert user.info.note_count == 1 @@ -51,7 +53,7 @@ defmodule Pleroma.Web.OStatusTest do    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.data["object"]) +    object = Object.normalize(activity)      assert activity.data["type"] == "Create"      assert object.data["type"] == "Note" @@ -65,7 +67,7 @@ defmodule Pleroma.Web.OStatusTest do    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.data["object"]) +    object = Object.normalize(activity)      assert activity.data["type"] == "Create"      assert object.data["type"] == "Note" @@ -78,7 +80,7 @@ defmodule Pleroma.Web.OStatusTest do    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.data["object"]) +    object = Object.normalize(activity)      assert object.data["tag"] == ["nsfw"]      assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"] @@ -95,7 +97,7 @@ defmodule Pleroma.Web.OStatusTest do      incoming = File.read!("test/fixtures/incoming_reply_mastodon.xml")      {:ok, [activity]} = OStatus.handle_incoming(incoming) -    object = Object.normalize(activity.data["object"]) +    object = Object.normalize(activity)      assert activity.data["type"] == "Create"      assert object.data["type"] == "Note" @@ -107,7 +109,7 @@ defmodule Pleroma.Web.OStatusTest do    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.data["object"]) +    object = Object.normalize(activity)      assert activity.data["type"] == "Create"      assert object.data["type"] == "Note" @@ -119,7 +121,7 @@ defmodule Pleroma.Web.OStatusTest do    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.data["object"]) +    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"] @@ -130,7 +132,7 @@ defmodule Pleroma.Web.OStatusTest do    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.data["object"]) +    retweeted_object = Object.normalize(retweeted_activity)      assert retweeted_object.data["summary"] == "Hey."    end @@ -138,7 +140,7 @@ defmodule Pleroma.Web.OStatusTest do    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.data["object"]) +    object = Object.normalize(activity)      assert activity.data["type"] == "Create"      assert object.data["type"] == "Note" @@ -164,7 +166,7 @@ defmodule Pleroma.Web.OStatusTest do      refute activity.local      retweeted_activity = Activity.get_by_id(retweeted_activity.id) -    retweeted_object = Object.normalize(retweeted_activity.data["object"]) +    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 @@ -176,18 +178,19 @@ defmodule Pleroma.Web.OStatusTest do    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", note_activity.data["object"]["id"]) +      |> 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"] == retweeted_activity.data["object"]["id"] +    assert activity.data["object"] == object.data["id"]      assert user.ap_id in activity.data["to"]      refute activity.local @@ -196,13 +199,13 @@ defmodule Pleroma.Web.OStatusTest do      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 +    assert Object.normalize(retweeted_activity).data["announcement_count"] == 1    end    test "handle incoming retweets - Mastodon, salmon" do      incoming = File.read!("test/fixtures/share.xml")      {:ok, [[activity, retweeted_activity]]} = OStatus.handle_incoming(incoming) -    retweeted_object = Object.normalize(retweeted_activity.data["object"]) +    retweeted_object = Object.normalize(retweeted_activity)      assert activity.data["type"] == "Announce"      assert activity.data["actor"] == "https://mastodon.social/users/lambadalambda" @@ -251,25 +254,29 @@ defmodule Pleroma.Web.OStatusTest do    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", note_activity.data["object"]["id"]) +      |> 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"] == favorited_activity.data["object"]["id"] +    assert activity.data["object"] == object.data["id"]      refute activity.local      assert note_activity.id == favorited_activity.id      assert favorited_activity.local    end -  test "handle incoming replies" do +  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.data["object"]) +    object = Object.normalize(activity, false)      assert activity.data["type"] == "Create"      assert object.data["type"] == "Note" @@ -282,6 +289,23 @@ defmodule Pleroma.Web.OStatusTest do      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 @@ -302,6 +326,14 @@ defmodule Pleroma.Web.OStatusTest do      assert User.following?(follower, followed)    end +  test "refuse following over OStatus if the followed's account is locked" do +    incoming = File.read!("test/fixtures/follow.xml") +    _user = insert(:user, info: %{locked: true}, ap_id: "https://pawoo.net/users/pekorino") + +    {:ok, [{:error, "It's not possible to follow locked accounts over OStatus"}]} = +      OStatus.handle_incoming(incoming) +  end +    test "handle incoming unfollows with existing follow" do      incoming_follow = File.read!("test/fixtures/follow.xml")      {:ok, [_activity]} = OStatus.handle_incoming(incoming_follow) @@ -315,13 +347,14 @@ defmodule Pleroma.Web.OStatusTest do               "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" -    assert is_map(activity.data["object"]) -    assert activity.data["object"]["type"] == "Follow" -    assert activity.data["object"]["object"] == "https://pawoo.net/users/pekorino" +    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(activity.data["object"]["object"]) +    followed = User.get_cached_by_ap_id(embedded_object["object"])      refute User.following?(follower, followed)    end @@ -401,7 +434,7 @@ defmodule Pleroma.Web.OStatusTest do                 }      end -    test "find_make_or_update_user takes an author element and returns an updated user" do +    test "find_make_or_update_actor takes an author element and returns an updated user" do        uri = "https://social.heldscal.la/user/23211"        {:ok, user} = OStatus.find_or_make_user(uri) @@ -414,14 +447,56 @@ defmodule Pleroma.Web.OStatusTest do        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) +      {:ok, user} = OStatus.find_make_or_update_actor(author)        assert user.avatar["type"] == "Image"        assert user.name == old_name        assert user.bio == old_bio -      {:ok, user_again} = OStatus.find_make_or_update_user(author) +      {:ok, user_again} = OStatus.find_make_or_update_actor(author)        assert user_again == user      end + +    test "find_or_make_user disallows protocol downgrade" do +      user = insert(:user, %{local: true}) +      {:ok, user} = OStatus.find_or_make_user(user.ap_id) + +      assert User.ap_enabled?(user) + +      user = +        insert(:user, %{ +          ap_id: "https://social.heldscal.la/user/23211", +          info: %{ap_enabled: true}, +          local: false +        }) + +      assert User.ap_enabled?(user) + +      {:ok, user} = OStatus.find_or_make_user(user.ap_id) +      assert User.ap_enabled?(user) +    end + +    test "find_make_or_update_actor disallows protocol downgrade" do +      user = insert(:user, %{local: true}) +      {:ok, user} = OStatus.find_or_make_user(user.ap_id) + +      assert User.ap_enabled?(user) + +      user = +        insert(:user, %{ +          ap_id: "https://social.heldscal.la/user/23211", +          info: %{ap_enabled: true}, +          local: false +        }) + +      assert User.ap_enabled?(user) + +      {:ok, user} = OStatus.find_or_make_user(user.ap_id) +      assert User.ap_enabled?(user) + +      doc = XML.parse_document(File.read!("test/fixtures/23211.atom")) +      [author] = :xmerl_xpath.string('//author[1]', doc) +      {:error, :invalid_protocol} = OStatus.find_make_or_update_actor(author) +    end    end    describe "gathering user info from a user id" do @@ -538,8 +613,7 @@ defmodule Pleroma.Web.OStatusTest do      test "Article objects are not representable" do        note_activity = insert(:note_activity) - -      note_object = Object.normalize(note_activity.data["object"]) +      note_object = Object.normalize(note_activity)        note_data =          note_object.data diff --git a/test/web/pleroma_api/pleroma_api_controller_test.exs b/test/web/pleroma_api/pleroma_api_controller_test.exs new file mode 100644 index 000000000..ed6b79727 --- /dev/null +++ b/test/web/pleroma_api/pleroma_api_controller_test.exs @@ -0,0 +1,94 @@ +# 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.Repo +  alias Pleroma.Web.CommonAPI + +  import Pleroma.Factory + +  test "/api/v1/pleroma/conversations/:id", %{conn: conn} do +    user = insert(:user) +    other_user = insert(:user) + +    {:ok, _activity} = +      CommonAPI.post(user, %{"status" => "Hi @#{other_user.nickname}!", "visibility" => "direct"}) + +    [participation] = Participation.for_user(other_user) + +    result = +      conn +      |> assign(:user, other_user) +      |> get("/api/v1/pleroma/conversations/#{participation.id}") +      |> json_response(200) + +    assert result["id"] == participation.id |> to_string() +  end + +  test "/api/v1/pleroma/conversations/:id/statuses", %{conn: conn} do +    user = insert(:user) +    other_user = insert(:user) +    third_user = insert(:user) + +    {:ok, _activity} = +      CommonAPI.post(user, %{"status" => "Hi @#{third_user.nickname}!", "visibility" => "direct"}) + +    {:ok, activity} = +      CommonAPI.post(user, %{"status" => "Hi @#{other_user.nickname}!", "visibility" => "direct"}) + +    [participation] = Participation.for_user(other_user) + +    {:ok, activity_two} = +      CommonAPI.post(other_user, %{ +        "status" => "Hi!", +        "in_reply_to_status_id" => activity.id, +        "in_reply_to_conversation_id" => participation.id +      }) + +    result = +      conn +      |> assign(:user, other_user) +      |> get("/api/v1/pleroma/conversations/#{participation.id}/statuses") +      |> json_response(200) + +    assert length(result) == 2 + +    id_one = activity.id +    id_two = activity_two.id +    assert [%{"id" => ^id_one}, %{"id" => ^id_two}] = result +  end + +  test "PATCH /api/v1/pleroma/conversations/:id", %{conn: conn} do +    user = insert(:user) +    other_user = insert(:user) + +    {:ok, _activity} = CommonAPI.post(user, %{"status" => "Hi", "visibility" => "direct"}) + +    [participation] = Participation.for_user(user) + +    participation = Repo.preload(participation, :recipients) + +    assert [user] == participation.recipients +    assert other_user not in participation.recipients + +    result = +      conn +      |> assign(:user, user) +      |> patch("/api/v1/pleroma/conversations/#{participation.id}", %{ +        "recipients" => [user.id, other_user.id] +      }) +      |> json_response(200) + +    assert result["id"] == participation.id |> to_string + +    [participation] = Participation.for_user(user) +    participation = Repo.preload(participation, :recipients) + +    assert user in participation.recipients +    assert other_user in participation.recipients +  end +end diff --git a/test/web/plugs/federating_plug_test.exs b/test/web/plugs/federating_plug_test.exs index 530562325..bb2e1687a 100644 --- a/test/web/plugs/federating_plug_test.exs +++ b/test/web/plugs/federating_plug_test.exs @@ -4,6 +4,7 @@  defmodule Pleroma.Web.FederatingPlugTest do    use Pleroma.Web.ConnCase +  clear_config_all([:instance, :federating])    test "returns and halt the conn when federating is disabled" do      Pleroma.Config.put([:instance, :federating], false) @@ -14,11 +15,11 @@ defmodule Pleroma.Web.FederatingPlugTest do      assert conn.status == 404      assert conn.halted - -    Pleroma.Config.put([:instance, :federating], true)    end    test "does nothing when federating is enabled" do +    Pleroma.Config.put([:instance, :federating], true) +      conn =        build_conn()        |> Pleroma.Web.FederatingPlug.call(%{}) diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs index 1e948086a..e2f89f40a 100644 --- a/test/web/push/impl_test.exs +++ b/test/web/push/impl_test.exs @@ -124,8 +124,7 @@ defmodule Pleroma.Web.Push.ImplTest do      {: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"    end    test "renders body for announce activity" do @@ -156,7 +155,6 @@ 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"    end  end diff --git a/test/web/rel_me_test.exs b/test/web/rel_me_test.exs index 5188f4de1..85515c432 100644 --- a/test/web/rel_me_test.exs +++ b/test/web/rel_me_test.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Web.RelMeTest do    use ExUnit.Case, async: true diff --git a/test/web/rich_media/aws_signed_url_test.exs b/test/web/rich_media/aws_signed_url_test.exs new file mode 100644 index 000000000..a3a50cbb1 --- /dev/null +++ b/test/web/rich_media/aws_signed_url_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.RichMedia.TTL.AwsSignedUrlTest do +  use ExUnit.Case, async: true + +  test "s3 signed url is parsed correct for expiration time" do +    url = "https://pleroma.social/amz" + +    {:ok, timestamp} = +      Timex.now() +      |> DateTime.truncate(:second) +      |> Timex.format("{ISO:Basic:Z}") + +    # in seconds +    valid_till = 30 + +    metadata = construct_metadata(timestamp, valid_till, url) + +    expire_time = +      Timex.parse!(timestamp, "{ISO:Basic:Z}") |> Timex.to_unix() |> Kernel.+(valid_till) + +    assert expire_time == Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl.ttl(metadata, url) +  end + +  test "s3 signed url is parsed and correct ttl is set for rich media" do +    url = "https://pleroma.social/amz" + +    {:ok, timestamp} = +      Timex.now() +      |> DateTime.truncate(:second) +      |> Timex.format("{ISO:Basic:Z}") + +    # in seconds +    valid_till = 30 + +    metadata = construct_metadata(timestamp, valid_till, url) + +    body = """ +    <meta name="twitter:card" content="Pleroma" /> +    <meta name="twitter:site" content="Pleroma" /> +    <meta name="twitter:title" content="Pleroma" /> +    <meta name="twitter:description" content="Pleroma" /> +    <meta name="twitter:image" content="#{Map.get(metadata, :image)}" /> +    """ + +    Tesla.Mock.mock(fn +      %{ +        method: :get, +        url: "https://pleroma.social/amz" +      } -> +        %Tesla.Env{status: 200, body: body} +    end) + +    Cachex.put(:rich_media_cache, url, metadata) + +    Pleroma.Web.RichMedia.Parser.set_ttl_based_on_image({:ok, metadata}, url) + +    {: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 +    # make it 2 seconds for flakyness +    assert_in_delta(valid_till * 1000, cache_ttl, 2000) +  end + +  defp construct_s3_url(timestamp, valid_till) do +    "https://pleroma.s3.ap-southeast-1.amazonaws.com/sachin%20%281%29%20_a%20-%25%2Aasdasd%20BNN%20bnnn%20.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIBLWWK6RGDQXDLJQ%2F20190716%2Fap-southeast-1%2Fs3%2Faws4_request&X-Amz-Date=#{ +      timestamp +    }&X-Amz-Expires=#{valid_till}&X-Amz-Signature=04ffd6b98634f4b1bbabc62e0fac4879093cd54a6eed24fe8eb38e8369526bbf&X-Amz-SignedHeaders=host" +  end + +  defp construct_metadata(timestamp, valid_till, url) do +    %{ +      image: construct_s3_url(timestamp, valid_till), +      site: "Pleroma", +      title: "Pleroma", +      description: "Pleroma", +      url: url +    } +  end +end diff --git a/test/web/rich_media/helpers_test.exs b/test/web/rich_media/helpers_test.exs index 53b0596f5..48884319d 100644 --- a/test/web/rich_media/helpers_test.exs +++ b/test/web/rich_media/helpers_test.exs @@ -1,17 +1,26 @@ +# 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.HelpersTest do    use Pleroma.DataCase +  alias Pleroma.Config    alias Pleroma.Object    alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.RichMedia.Helpers    import Pleroma.Factory    import Tesla.Mock    setup do      mock(fn env -> apply(HttpRequestMock, :request, [env]) end) +      :ok    end +  clear_config([:rich_media, :enabled]) +    test "refuses to crawl incomplete URLs" do      user = insert(:user) @@ -21,11 +30,9 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do          "content_type" => "text/markdown"        }) -    Pleroma.Config.put([:rich_media, :enabled], true) +    Config.put([:rich_media, :enabled], true)      assert %{} == Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) - -    Pleroma.Config.put([:rich_media, :enabled], false)    end    test "refuses to crawl malformed URLs" do @@ -37,11 +44,9 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do          "content_type" => "text/markdown"        }) -    Pleroma.Config.put([:rich_media, :enabled], true) +    Config.put([:rich_media, :enabled], true)      assert %{} == Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) - -    Pleroma.Config.put([:rich_media, :enabled], false)    end    test "crawls valid, complete URLs" do @@ -49,16 +54,14 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do      {:ok, activity} =        CommonAPI.post(user, %{ -        "status" => "[test](http://example.com/ogp)", +        "status" => "[test](https://example.com/ogp)",          "content_type" => "text/markdown"        }) -    Pleroma.Config.put([:rich_media, :enabled], true) +    Config.put([:rich_media, :enabled], true) -    assert %{page_url: "http://example.com/ogp", rich_media: _} = +    assert %{page_url: "https://example.com/ogp", rich_media: _} =               Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) - -    Pleroma.Config.put([:rich_media, :enabled], false)    end    test "refuses to crawl URLs from posts marked sensitive" do @@ -74,11 +77,9 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do      assert object.data["sensitive"] -    Pleroma.Config.put([:rich_media, :enabled], true) +    Config.put([:rich_media, :enabled], true)      assert %{} = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) - -    Pleroma.Config.put([:rich_media, :enabled], false)    end    test "refuses to crawl URLs from posts tagged NSFW" do @@ -93,10 +94,28 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do      assert object.data["sensitive"] -    Pleroma.Config.put([:rich_media, :enabled], true) +    Config.put([:rich_media, :enabled], true)      assert %{} = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) +  end + +  test "refuses to crawl URLs of private network from posts" do +    user = insert(:user) + +    {:ok, activity} = +      CommonAPI.post(user, %{"status" => "http://127.0.0.1:4000/notice/9kCP7VNyPJXFOXDrgO"}) + +    {:ok, activity2} = CommonAPI.post(user, %{"status" => "https://10.111.10.1/notice/9kCP7V"}) +    {:ok, activity3} = CommonAPI.post(user, %{"status" => "https://172.16.32.40/notice/9kCP7V"}) +    {:ok, activity4} = CommonAPI.post(user, %{"status" => "https://192.168.10.40/notice/9kCP7V"}) +    {:ok, activity5} = CommonAPI.post(user, %{"status" => "https://pleroma.local/notice/9kCP7V"}) + +    Config.put([:rich_media, :enabled], true) -    Pleroma.Config.put([:rich_media, :enabled], false) +    assert %{} = Helpers.fetch_data_for_activity(activity) +    assert %{} = Helpers.fetch_data_for_activity(activity2) +    assert %{} = Helpers.fetch_data_for_activity(activity3) +    assert %{} = Helpers.fetch_data_for_activity(activity4) +    assert %{} = Helpers.fetch_data_for_activity(activity5)    end  end diff --git a/test/web/rich_media/parser_test.exs b/test/web/rich_media/parser_test.exs index 3a9cc1854..b75bdf96f 100644 --- a/test/web/rich_media/parser_test.exs +++ b/test/web/rich_media/parser_test.exs @@ -1,3 +1,7 @@ +# 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.ParserTest do    use ExUnit.Case, async: true @@ -11,6 +15,21 @@ defmodule Pleroma.Web.RichMedia.ParserTest do        %{          method: :get, +        url: "http://example.com/non-ogp" +      } -> +        %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/non_ogp_embed.html")} + +      %{ +        method: :get, +        url: "http://example.com/ogp-missing-title" +      } -> +        %Tesla.Env{ +          status: 200, +          body: File.read!("test/fixtures/rich_media/ogp-missing-title.html") +        } + +      %{ +        method: :get,          url: "http://example.com/twitter-card"        } ->          %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/twitter_card.html")} @@ -38,6 +57,12 @@ defmodule Pleroma.Web.RichMedia.ParserTest do      assert {:error, _} = Pleroma.Web.RichMedia.Parser.parse("http://example.com/empty")    end +  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: %{url: \"http://example.com/non-ogp\"}"} +  end +    test "parses ogp" do      assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/ogp") ==               {:ok, @@ -47,7 +72,20 @@ 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 + +  test "falls back to <title> when ogp:title is missing" do +    assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/ogp-missing-title") == +             {:ok, +              %{ +                image: "http://ia.media-imdb.com/images/rock.jpg", +                title: "The Rock (1996)", +                description: +                  "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.", +                type: "video.movie", +                url: "http://example.com/ogp-missing-title"                }}    end @@ -59,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 @@ -83,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/streamer_test.exs b/test/web/streamer_test.exs index bfe18cb7f..96fa7645f 100644 --- a/test/web/streamer_test.exs +++ b/test/web/streamer_test.exs @@ -11,6 +11,111 @@ defmodule Pleroma.Web.StreamerTest do    alias Pleroma.Web.Streamer    import Pleroma.Factory +  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}} +    end + +    test "it sends notify to in the 'user' stream", %{user: user, notify: notify} do +      task = +        Task.async(fn -> +          assert_receive {:text, _}, 4_000 +        end) + +      Streamer.add_socket( +        "user", +        %{transport_pid: task.pid, assigns: %{user: user}} +      ) + +      Streamer.stream("user", notify) +      Task.await(task) +    end + +    test "it sends notify to in the 'user:notification' stream", %{user: user, notify: notify} do +      task = +        Task.async(fn -> +          assert_receive {:text, _}, 4_000 +        end) + +      Streamer.add_socket( +        "user:notification", +        %{transport_pid: task.pid, assigns: %{user: user}} +      ) + +      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} = User.block(user, blocked) + +      task = Task.async(fn -> refute_receive {:text, _}, 4_000 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, _}, 4_000 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, _}, 4_000 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 +  end +    test "it sends to public" do      user = insert(:user)      other_user = insert(:user) @@ -68,6 +173,74 @@ defmodule Pleroma.Web.StreamerTest do      Task.await(task)    end +  describe "thread_containment" 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]) + +      activity = +        insert(:note_activity, +          note: +            insert(:note, +              user: author, +              data: %{"to" => ["TEST-FFF"]} +            ) +        ) + +      task = Task.async(fn -> refute_receive {:text, _}, 1_000 end) +      fake_socket = %{transport_pid: task.pid, assigns: %{user: user}} +      topics = %{"public" => [fake_socket]} +      Streamer.push_to_socket(topics, "public", activity) + +      Task.await(task) +    end + +    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]) + +      activity = +        insert(:note_activity, +          note: +            insert(:note, +              user: author, +              data: %{"to" => ["TEST-FFF"]} +            ) +        ) + +      task = Task.async(fn -> assert_receive {:text, _}, 1_000 end) +      fake_socket = %{transport_pid: task.pid, assigns: %{user: user}} +      topics = %{"public" => [fake_socket]} +      Streamer.push_to_socket(topics, "public", activity) + +      Task.await(task) +    end + +    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}) + +      activity = +        insert(:note_activity, +          note: +            insert(:note, +              user: author, +              data: %{"to" => ["TEST-FFF"]} +            ) +        ) + +      task = Task.async(fn -> assert_receive {:text, _}, 1_000 end) +      fake_socket = %{transport_pid: task.pid, assigns: %{user: user}} +      topics = %{"public" => [fake_socket]} +      Streamer.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) @@ -232,4 +405,130 @@ defmodule Pleroma.Web.StreamerTest do      Task.await(task)    end + +  test "it doesn't send posts from muted threads" do +    user = insert(:user) +    user2 = insert(:user) +    {:ok, user2, user, _activity} = CommonAPI.follow(user2, user) + +    {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) + +    {:ok, activity} = CommonAPI.add_mute(user2, activity) + +    task = Task.async(fn -> refute_receive {:text, _}, 4_000 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 +      GenServer.start(Streamer, %{}, name: Streamer) + +      on_exit(fn -> +        if pid = Process.whereis(Streamer) do +          Process.exit(pid, :kill) +        end +      end) + +      :ok +    end + +    test "it sends conversation update to the 'direct' stream", %{} do +      user = insert(:user) +      another_user = insert(:user) + +      task = +        Task.async(fn -> +          assert_receive {:text, _received_event}, 4_000 +        end) + +      Streamer.add_socket( +        "direct", +        %{transport_pid: task.pid, assigns: %{user: user}} +      ) + +      {:ok, _create_activity} = +        CommonAPI.post(another_user, %{ +          "status" => "hey @#{user.nickname}", +          "visibility" => "direct" +        }) + +      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 +      user = insert(:user) +      another_user = insert(:user) + +      {:ok, create_activity} = +        CommonAPI.post(another_user, %{ +          "status" => "hi @#{user.nickname}", +          "visibility" => "direct" +        }) + +      task = +        Task.async(fn -> +          assert_receive {:text, received_event}, 4_000 +          assert %{"event" => "delete", "payload" => _} = Jason.decode!(received_event) + +          refute_receive {:text, _}, 4_000 +        end) + +      Streamer.add_socket( +        "direct", +        %{transport_pid: task.pid, assigns: %{user: user}} +      ) + +      {:ok, _} = CommonAPI.delete(create_activity.id, another_user) + +      Task.await(task) +    end + +    test "it sends conversation update to the 'direct' stream when a message is deleted" do +      user = insert(:user) +      another_user = insert(:user) + +      {:ok, create_activity} = +        CommonAPI.post(another_user, %{ +          "status" => "hi @#{user.nickname}", +          "visibility" => "direct" +        }) + +      {:ok, create_activity2} = +        CommonAPI.post(another_user, %{ +          "status" => "hi @#{user.nickname}", +          "in_reply_to_status_id" => create_activity.id, +          "visibility" => "direct" +        }) + +      task = +        Task.async(fn -> +          assert_receive {:text, received_event}, 4_000 +          assert %{"event" => "delete", "payload" => _} = Jason.decode!(received_event) + +          assert_receive {:text, received_event}, 4_000 + +          assert %{"event" => "conversation", "payload" => received_payload} = +                   Jason.decode!(received_event) + +          assert %{"last_status" => last_status} = Jason.decode!(received_payload) +          assert last_status["id"] == to_string(create_activity.id) +        end) + +      Streamer.add_socket( +        "direct", +        %{transport_pid: task.pid, assigns: %{user: user}} +      ) + +      {:ok, _} = CommonAPI.delete(create_activity2.id, another_user) + +      Task.await(task) +    end +  end  end diff --git a/test/web/twitter_api/password_controller_test.exs b/test/web/twitter_api/password_controller_test.exs new file mode 100644 index 000000000..3a7246ea8 --- /dev/null +++ b/test/web/twitter_api/password_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.TwitterAPI.PasswordControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.PasswordResetToken +  alias Pleroma.Web.OAuth.Token +  import Pleroma.Factory + +  describe "GET /api/pleroma/password_reset/token" do +    test "it returns error when token invalid", %{conn: conn} do +      response = +        conn +        |> get("/api/pleroma/password_reset/token") +        |> html_response(:ok) + +      assert response =~ "<h2>Invalid Token</h2>" +    end + +    test "it shows password reset form", %{conn: conn} do +      user = insert(:user) +      {:ok, token} = PasswordResetToken.create_token(user) + +      response = +        conn +        |> get("/api/pleroma/password_reset/#{token.token}") +        |> html_response(:ok) + +      assert response =~ "<h2>Password Reset for #{user.nickname}</h2>" +    end +  end + +  describe "POST /api/pleroma/password_reset" do +    test "it returns HTTP 200", %{conn: conn} do +      user = insert(:user) +      {: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 +      } + +      response = +        conn +        |> assign(:user, user) +        |> post("/api/pleroma/password_reset", %{data: params}) +        |> html_response(:ok) + +      assert response =~ "<h2>Password changed!</h2>" + +      user = refresh_record(user) +      assert Comeonin.Pbkdf2.checkpw("test", user.password_hash) +      assert length(Token.get_user_tokens(user)) == 0 +    end +  end +end diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/web/twitter_api/twitter_api_controller_test.exs index bcd0f522d..8ef14b4c5 100644 --- a/test/web/twitter_api/twitter_api_controller_test.exs +++ b/test/web/twitter_api/twitter_api_controller_test.exs @@ -40,6 +40,18 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        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 @@ -54,6 +66,18 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        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 @@ -127,6 +151,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do    describe "GET /statuses/public_timeline.json" do      setup [:valid_user] +    clear_config([:instance, :public])      test "returns statuses", %{conn: conn} do        user = insert(:user) @@ -149,8 +174,6 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        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", @@ -161,8 +184,6 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        |> 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 @@ -196,6 +217,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do    describe "GET /statuses/public_and_external_timeline.json" do      setup [:valid_user] +    clear_config([:instance, :public])      test "returns 403 to unauthenticated request when the instance is not public", %{conn: conn} do        Pleroma.Config.put([:instance, :public], false) @@ -203,8 +225,6 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        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", @@ -215,8 +235,6 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        |> 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 @@ -497,6 +515,38 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do                   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 @@ -821,6 +871,19 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        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 @@ -892,7 +955,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do      test "with credentials", %{conn: conn, user: current_user} do        note_activity = insert(:note_activity) -      object = Object.get_by_ap_id(note_activity.data["object"]["id"]) +      object = Object.normalize(note_activity)        ActivityPub.like(current_user, object)        conn = @@ -1047,15 +1110,17 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do    describe "POST /api/account/password_reset, with invalid parameters" do      setup [:valid_user] -    test "it returns 500 when user is not found", %{conn: conn, user: user} do +    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 json_response(conn, :internal_server_error) +      assert conn.status == 404 +      assert conn.resp_body == ""      end -    test "it returns 500 when user is not local", %{conn: conn, user: user} do +    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 json_response(conn, :internal_server_error) +      assert conn.status == 400 +      assert conn.resp_body == ""      end    end @@ -1105,13 +1170,6 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do    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) @@ -1126,6 +1184,10 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        [user: user]      end +    clear_config([:instance, :account_activation_required]) do +      Pleroma.Config.put([:instance, :account_activation_required], true) +    end +      test "it returns 204 No Content", %{conn: conn, user: user} do        conn        |> assign(:user, user) @@ -1495,7 +1557,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do            "hide_follows" => "false"          }) -      user = Repo.get!(User, user.id) +      user = refresh_record(user)        assert user.info.hide_follows == false        assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})      end @@ -1548,6 +1610,29 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        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) diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs index d601c8f1f..cbe83852e 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -46,7 +46,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do      }      {:ok, activity = %Activity{}} = TwitterAPI.create_status(user, input) -    object = Object.normalize(activity.data["object"]) +    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>" @@ -91,7 +91,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do      }      {:ok, activity = %Activity{}} = TwitterAPI.create_status(user, input) -    object = Object.normalize(activity.data["object"]) +    object = Object.normalize(activity)      input = %{        "status" => "Here's your (you).", @@ -99,7 +99,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do      }      {:ok, reply = %Activity{}} = TwitterAPI.create_status(user, input) -    reply_object = Object.normalize(reply.data["object"]) +    reply_object = Object.normalize(reply)      assert get_in(reply.data, ["context"]) == get_in(activity.data, ["context"]) @@ -116,8 +116,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do      {:ok, user, followed, _activity} = TwitterAPI.follow(user, %{"user_id" => followed.id})      assert User.ap_followers(followed) in user.following -    {:error, msg} = TwitterAPI.follow(user, %{"user_id" => followed.id}) -    assert msg == "Could not follow user: #{followed.nickname} is already on your list." +    {:ok, _, _, _} = TwitterAPI.follow(user, %{"user_id" => followed.id})    end    test "Follow another user using screen_name" do @@ -132,8 +131,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do      followed = User.get_cached_by_ap_id(followed.ap_id)      assert followed.info.follower_count == 1 -    {:error, msg} = TwitterAPI.follow(user, %{"screen_name" => followed.nickname}) -    assert msg == "Could not follow user: #{followed.nickname} is already on your list." +    {:ok, _, _, _} = TwitterAPI.follow(user, %{"screen_name" => followed.nickname})    end    test "Unfollow another user using user_id" do @@ -218,7 +216,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do      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.data["object"]) +    object = Object.normalize(note_activity)      assert object.data["like_count"] == 1 @@ -226,7 +224,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do      {:ok, _status} = TwitterAPI.fav(other_user, note_activity.id) -    object = Object.normalize(note_activity.data["object"]) +    object = Object.normalize(note_activity)      assert object.data["like_count"] == 2 @@ -237,7 +235,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do    test "it unfavorites a status, returns the updated activity" do      user = insert(:user)      note_activity = insert(:note_activity) -    object = Object.get_by_ap_id(note_activity.data["object"]["id"]) +    object = Object.normalize(note_activity)      {:ok, _like_activity, _object} = ActivityPub.like(user, object)      updated_activity = Activity.get_by_ap_id(note_activity.data["id"]) diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs index 2cd82b3e7..fe4ffdb59 100644 --- a/test/web/twitter_api/util_controller_test.exs +++ b/test/web/twitter_api/util_controller_test.exs @@ -1,3 +1,7 @@ +# 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.UtilControllerTest do    use Pleroma.Web.ConnCase @@ -6,12 +10,17 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do    alias Pleroma.User    alias Pleroma.Web.CommonAPI    import Pleroma.Factory +  import Mock    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 "POST /api/pleroma/follow_import" do      test "it returns HTTP 200", %{conn: conn} do        user1 = insert(:user) @@ -26,6 +35,35 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do        assert response == "job started"      end +    test "it imports follow lists from file", %{conn: conn} do +      user1 = insert(:user) +      user2 = insert(:user) + +      with_mocks([ +        {File, [], +         read!: fn "follow_list.txt" -> +           "Account address,Show boosts\n#{user2.ap_id},true" +         end}, +        {PleromaJobQueue, [:passthrough], []} +      ]) do +        response = +          conn +          |> assign(:user, user1) +          |> post("/api/pleroma/follow_import", %{"list" => %Plug.Upload{path: "follow_list.txt"}}) +          |> json_response(:ok) + +        assert called( +                 PleromaJobQueue.enqueue( +                   :background, +                   User, +                   [:follow_import, user1, [user2.ap_id]] +                 ) +               ) + +        assert response == "job started" +      end +    end +      test "it imports new-style mastodon follow lists", %{conn: conn} do        user1 = insert(:user)        user2 = insert(:user) @@ -74,6 +112,33 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do        assert response == "job started"      end + +    test "it imports blocks users from file", %{conn: conn} do +      user1 = insert(:user) +      user2 = insert(:user) +      user3 = insert(:user) + +      with_mocks([ +        {File, [], read!: fn "blocks_list.txt" -> "#{user2.ap_id} #{user3.ap_id}" end}, +        {PleromaJobQueue, [:passthrough], []} +      ]) do +        response = +          conn +          |> assign(:user, user1) +          |> post("/api/pleroma/blocks_import", %{"list" => %Plug.Upload{path: "blocks_list.txt"}}) +          |> json_response(:ok) + +        assert called( +                 PleromaJobQueue.enqueue( +                   :background, +                   User, +                   [:blocks_import, user1, [user2.ap_id, user3.ap_id]] +                 ) +               ) + +        assert response == "job started" +      end +    end    end    describe "POST /api/pleroma/notifications/read" do @@ -93,6 +158,18 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do        assert Repo.get(Notification, notification1.id).seen        refute Repo.get(Notification, notification2.id).seen      end + +    test "it returns error when notification not found", %{conn: conn} do +      user1 = insert(:user) + +      response = +        conn +        |> assign(:user, user1) +        |> post("/api/pleroma/notifications/read", %{"id" => "22222222222222"}) +        |> json_response(403) + +      assert response == %{"error" => "Cannot get notification"} +    end    end    describe "PUT /api/pleroma/notification_settings" do @@ -102,7 +179,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do        conn        |> assign(:user, user)        |> put("/api/pleroma/notification_settings", %{ -        "remote" => false,          "followers" => false,          "bar" => 1        }) @@ -110,14 +186,73 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do        user = Repo.get(User, user.id) -      assert %{"remote" => false, "local" => true, "followers" => false, "follows" => true} == -               user.info.notification_settings +      assert %{ +               "followers" => false, +               "follows" => true, +               "non_follows" => true, +               "non_followers" => true +             } == user.info.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 = @@ -135,8 +270,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 @@ -202,7 +335,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do      end    end -  describe "GET /ostatus_subscribe?acct=...." do +  describe "GET /ostatus_subscribe - remote_follow/2" do      test "adds status to pleroma instance if the `acct` is a status", %{conn: conn} do        conn =          get( @@ -222,12 +355,227 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do        assert html_response(response, 200) =~ "Log in to follow"      end + +    test "show follow page if the `acct` is a account link", %{conn: conn} do +      user = insert(:user) + +      response = +        conn +        |> assign(:user, user) +        |> get("/ostatus_subscribe?acct=https://mastodon.social/users/emelie") + +      assert html_response(response, 200) =~ "Remote follow" +    end + +    test "show follow page with error when user cannot fecth by `acct` link", %{conn: conn} do +      user = insert(:user) + +      response = +        conn +        |> assign(:user, user) +        |> get("/ostatus_subscribe?acct=https://mastodon.social/users/not_found") + +      assert html_response(response, 200) =~ "Error fetching user" +    end    end -  test "GET /api/pleroma/healthcheck", %{conn: conn} do -    conn = get(conn, "/api/pleroma/healthcheck") +  describe "POST /ostatus_subscribe - do_remote_follow/2 with assigned user " do +    test "follows user", %{conn: conn} do +      user = insert(:user) +      user2 = insert(:user) + +      response = +        conn +        |> assign(:user, user) +        |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) +        |> response(200) + +      assert response =~ "Account followed!" +      assert user2.follower_address in refresh_record(user).following +    end + +    test "returns error when user is deactivated", %{conn: conn} do +      user = insert(:user, info: %{deactivated: true}) +      user2 = insert(:user) + +      response = +        conn +        |> assign(:user, user) +        |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) +        |> response(200) + +      assert response =~ "Error following account" +    end + +    test "returns error when user is blocked", %{conn: conn} do +      Pleroma.Config.put([:user, :deny_follow_blocked], true) +      user = insert(:user) +      user2 = insert(:user) + +      {:ok, _user} = Pleroma.User.block(user2, user) + +      response = +        conn +        |> assign(:user, user) +        |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) +        |> response(200) + +      assert response =~ "Error following account" +    end + +    test "returns error when followee not found", %{conn: conn} do +      user = insert(:user) + +      response = +        conn +        |> assign(:user, user) +        |> post("/ostatus_subscribe", %{"user" => %{"id" => "jimm"}}) +        |> response(200) + +      assert response =~ "Error following account" +    end -    assert conn.status in [200, 503] +    test "returns success result when user already in followers", %{conn: conn} do +      user = insert(:user) +      user2 = insert(:user) +      {:ok, _, _, _} = CommonAPI.follow(user, user2) + +      response = +        conn +        |> assign(:user, refresh_record(user)) +        |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) +        |> response(200) + +      assert response =~ "Account followed!" +    end +  end + +  describe "POST /ostatus_subscribe - do_remote_follow/2 without assigned user " do +    test "follows", %{conn: conn} do +      user = insert(:user) +      user2 = insert(:user) + +      response = +        conn +        |> post("/ostatus_subscribe", %{ +          "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} +        }) +        |> response(200) + +      assert response =~ "Account followed!" +      assert user2.follower_address in refresh_record(user).following +    end + +    test "returns error when followee not found", %{conn: conn} do +      user = insert(:user) + +      response = +        conn +        |> post("/ostatus_subscribe", %{ +          "authorization" => %{"name" => user.nickname, "password" => "test", "id" => "jimm"} +        }) +        |> response(200) + +      assert response =~ "Error following account" +    end + +    test "returns error when login invalid", %{conn: conn} do +      user = insert(:user) + +      response = +        conn +        |> post("/ostatus_subscribe", %{ +          "authorization" => %{"name" => "jimm", "password" => "test", "id" => user.id} +        }) +        |> response(200) + +      assert response =~ "Wrong username or password" +    end + +    test "returns error when password invalid", %{conn: conn} do +      user = insert(:user) +      user2 = insert(:user) + +      response = +        conn +        |> post("/ostatus_subscribe", %{ +          "authorization" => %{"name" => user.nickname, "password" => "42", "id" => user2.id} +        }) +        |> response(200) + +      assert response =~ "Wrong username or password" +    end + +    test "returns error when user is blocked", %{conn: conn} do +      Pleroma.Config.put([:user, :deny_follow_blocked], true) +      user = insert(:user) +      user2 = insert(:user) +      {:ok, _user} = Pleroma.User.block(user2, user) + +      response = +        conn +        |> post("/ostatus_subscribe", %{ +          "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} +        }) +        |> response(200) + +      assert response =~ "Error following account" +    end +  end + +  describe "GET /api/pleroma/healthcheck" do +    clear_config([:instance, :healthcheck]) + +    test "returns 503 when healthcheck disabled", %{conn: conn} do +      Pleroma.Config.put([:instance, :healthcheck], false) + +      response = +        conn +        |> get("/api/pleroma/healthcheck") +        |> json_response(503) + +      assert response == %{} +    end + +    test "returns 200 when healthcheck enabled and all ok", %{conn: conn} do +      Pleroma.Config.put([:instance, :healthcheck], true) + +      with_mock Pleroma.Healthcheck, +        system_info: fn -> %Pleroma.Healthcheck{healthy: true} end do +        response = +          conn +          |> get("/api/pleroma/healthcheck") +          |> json_response(200) + +        assert %{ +                 "active" => _, +                 "healthy" => true, +                 "idle" => _, +                 "memory_used" => _, +                 "pool_size" => _ +               } = response +      end +    end + +    test "returns 503 when healthcheck enabled and  health is false", %{conn: conn} do +      Pleroma.Config.put([:instance, :healthcheck], true) + +      with_mock Pleroma.Healthcheck, +        system_info: fn -> %Pleroma.Healthcheck{healthy: false} end do +        response = +          conn +          |> get("/api/pleroma/healthcheck") +          |> json_response(503) + +        assert %{ +                 "active" => _, +                 "healthy" => false, +                 "idle" => _, +                 "memory_used" => _, +                 "pool_size" => _ +               } = response +      end +    end    end    describe "POST /api/pleroma/disable_account" do @@ -246,5 +594,104 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do        assert user.info.deactivated == true      end + +    test "it returns returns when password invalid", %{conn: conn} do +      user = insert(:user) + +      response = +        conn +        |> assign(:user, user) +        |> post("/api/pleroma/disable_account", %{"password" => "test1"}) +        |> json_response(:ok) + +      assert response == %{"error" => "Invalid password."} +      user = User.get_cached_by_id(user.id) + +      refute user.info.deactivated +    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 use 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  end diff --git a/test/web/twitter_api/views/activity_view_test.exs b/test/web/twitter_api/views/activity_view_test.exs index 43bd77f78..56d861efb 100644 --- a/test/web/twitter_api/views/activity_view_test.exs +++ b/test/web/twitter_api/views/activity_view_test.exs @@ -126,7 +126,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do      other_user = insert(:user, %{nickname: "shp"})      {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"}) -    object = Object.normalize(activity.data["object"]) +    object = Object.normalize(activity)      result = ActivityView.render("activity.json", activity: activity) @@ -177,7 +177,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do      user = insert(:user)      other_user = insert(:user, %{nickname: "shp"})      {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"}) -    object = Object.normalize(activity.data["object"]) +    object = Object.normalize(activity)      convo_id = Utils.context_to_conversation_id(object.data["context"]) @@ -351,7 +351,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do        "is_post_verb" => false,        "statusnet_html" => "deleted notice {{tag",        "text" => "deleted notice {{tag", -      "uri" => delete.data["object"], +      "uri" => Object.normalize(delete).data["id"],        "user" => UserView.render("show.json", user: user)      } diff --git a/test/web/twitter_api/views/user_view_test.exs b/test/web/twitter_api/views/user_view_test.exs index 74526673c..70c5a0b7f 100644 --- a/test/web/twitter_api/views/user_view_test.exs +++ b/test/web/twitter_api/views/user_view_test.exs @@ -99,7 +99,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do        "fields" => [],        "pleroma" => %{          "confirmation_pending" => false, -        "tags" => [] +        "tags" => [], +        "skip_thread_containment" => false        },        "rights" => %{"admin" => false, "delete_others_notice" => false},        "role" => "member" @@ -112,9 +113,11 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest 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 @@ -152,7 +155,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do        "fields" => [],        "pleroma" => %{          "confirmation_pending" => false, -        "tags" => [] +        "tags" => [], +        "skip_thread_containment" => false        },        "rights" => %{"admin" => false, "delete_others_notice" => false},        "role" => "member" @@ -197,7 +201,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do        "fields" => [],        "pleroma" => %{          "confirmation_pending" => false, -        "tags" => [] +        "tags" => [], +        "skip_thread_containment" => false        },        "rights" => %{"admin" => false, "delete_others_notice" => false},        "role" => "member" @@ -279,7 +284,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do        "fields" => [],        "pleroma" => %{          "confirmation_pending" => false, -        "tags" => [] +        "tags" => [], +        "skip_thread_containment" => false        },        "rights" => %{"admin" => false, "delete_others_notice" => false},        "role" => "member" diff --git a/test/web/uploader_controller_test.exs b/test/web/uploader_controller_test.exs new file mode 100644 index 000000000..70028df1c --- /dev/null +++ b/test/web/uploader_controller_test.exs @@ -0,0 +1,43 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.UploaderControllerTest do +  use Pleroma.Web.ConnCase +  alias Pleroma.Uploaders.Uploader + +  describe "callback/2" do +    test "it returns 400 response when process callback isn't alive", %{conn: conn} do +      res = +        conn +        |> post(uploader_path(conn, :callback, "test-path")) + +      assert res.status == 400 +      assert res.resp_body == "{\"error\":\"bad request\"}" +    end + +    test "it returns success result", %{conn: conn} do +      task = +        Task.async(fn -> +          receive do +            {Uploader, pid, conn, _params} -> +              conn = +                conn +                |> put_status(:ok) +                |> Phoenix.Controller.json(%{upload_path: "test-path"}) + +              send(pid, {Uploader, conn}) +          end +        end) + +      :global.register_name({Uploader, "test-path"}, task.pid) + +      res = +        conn +        |> post(uploader_path(conn, :callback, "test-path")) +        |> json_response(200) + +      assert res == %{"upload_path" => "test-path"} +    end +  end +end diff --git a/test/web/web_finger/web_finger_controller_test.exs b/test/web/web_finger/web_finger_controller_test.exs index 43fccfc7a..e23086b2a 100644 --- a/test/web/web_finger/web_finger_controller_test.exs +++ b/test/web/web_finger/web_finger_controller_test.exs @@ -13,6 +13,23 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do      :ok    end +  clear_config_all([:instance, :federating]) do +    Pleroma.Config.put([:instance, :federating], true) +  end + +  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      user = insert(:user) @@ -24,6 +41,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) @@ -35,6 +62,26 @@ 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_raise Phoenix.NotAcceptableError, fn -> +      build_conn() +      |> put_req_header("accept", "text/html") +      |> get("/.well-known/webfinger?resource=acct:#{user.nickname}@localhost") +    end +  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 335c95b18..8fdb9adea 100644 --- a/test/web/web_finger/web_finger_test.exs +++ b/test/web/web_finger/web_finger_test.exs @@ -40,6 +40,11 @@ defmodule Pleroma.Web.WebFingerTest do    end    describe "fingering" do +    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 info for an OStatus user" do        user = "shp@social.heldscal.la" @@ -81,6 +86,20 @@ defmodule Pleroma.Web.WebFingerTest do        assert data["subscribe_address"] == "https://gnusocial.de/main/ostatussub?profile={uri}"      end +    test "it work for AP-only user" do +      user = "kpherox@mstdn.jp" + +      {:ok, data} = WebFinger.finger(user) + +      assert data["magic_key"] == nil +      assert data["salmon"] == nil + +      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        user = "lain@squeet.me" @@ -104,5 +123,16 @@ defmodule Pleroma.Web.WebFingerTest do        assert template == "http://status.alpicola.com/main/xrd?uri={uri}"      end + +    test "it works with idna domains as nickname" do +      nickname = "lain@" <> to_string(:idna.encode("zetsubou.みんな")) + +      {:ok, _data} = WebFinger.finger(nickname) +    end + +    test "it works with idna domains as link" do +      ap_id = "https://" <> to_string(:idna.encode("zetsubou.みんな")) <> "/users/lain" +      {:ok, _data} = WebFinger.finger(ap_id) +    end    end  end diff --git a/test/web/websub/websub_controller_test.exs b/test/web/websub/websub_controller_test.exs index f79745d58..59cacbe68 100644 --- a/test/web/websub/websub_controller_test.exs +++ b/test/web/websub/websub_controller_test.exs @@ -9,6 +9,10 @@ defmodule Pleroma.Web.Websub.WebsubControllerTest do    alias Pleroma.Web.Websub    alias Pleroma.Web.Websub.WebsubClientSubscription +  clear_config_all([:instance, :federating]) do +    Pleroma.Config.put([:instance, :federating], true) +  end +    test "websub subscription request", %{conn: conn} do      user = insert(:user)  | 
