diff options
Diffstat (limited to 'test')
21 files changed, 1145 insertions, 31 deletions
| diff --git a/test/fixtures/config/temp.exported_from_db.secret.exs b/test/fixtures/config/temp.exported_from_db.secret.exs new file mode 100644 index 000000000..64bee7f32 --- /dev/null +++ b/test/fixtures/config/temp.exported_from_db.secret.exs @@ -0,0 +1,5 @@ +use Mix.Config + +config :pleroma, exported_config_merged: true + +config :pleroma, :first_setting, key: "new value" diff --git a/test/fixtures/mastodon/collections/featured.json b/test/fixtures/mastodon/collections/featured.json new file mode 100644 index 000000000..56f8f56fa --- /dev/null +++ b/test/fixtures/mastodon/collections/featured.json @@ -0,0 +1,39 @@ +{ +  "@context": [ +    "https://www.w3.org/ns/activitystreams", +    "https://{{domain}}/schemas/litepub-0.1.jsonld", +    { +      "@language": "und" +    } +  ], +  "id": "https://{{domain}}/users/{{nickname}}/collections/featured", +  "orderedItems": [ +    { +      "@context": [ +        "https://www.w3.org/ns/activitystreams", +        "https://{{domain}}/schemas/litepub-0.1.jsonld", +        { +          "@language": "und" +        } +      ], +      "actor": "https://{{domain}}/users/{{nickname}}", +      "attachment": [], +      "attributedTo": "https://{{domain}}/users/{{nickname}}", +      "cc": [ +        "https://{{domain}}/users/{{nickname}}/followers" +      ], +      "content": "", +      "id": "https://{{domain}}/objects/{{object_id}}", +      "published": "2021-02-12T15:13:43.915429Z", +      "sensitive": false, +      "source": "", +      "summary": "", +      "tag": [], +      "to": [ +        "https://www.w3.org/ns/activitystreams#Public" +      ], +      "type": "Note" +    } +  ], +  "type": "OrderedCollection" +} diff --git a/test/fixtures/statuses/masto-note.json b/test/fixtures/statuses/masto-note.json new file mode 100644 index 000000000..6b96de473 --- /dev/null +++ b/test/fixtures/statuses/masto-note.json @@ -0,0 +1,47 @@ +{ +  "@context": [ +    "https://www.w3.org/ns/activitystreams", +    { +      "ostatus": "http://ostatus.org#", +      "atomUri": "ostatus:atomUri", +      "inReplyToAtomUri": "ostatus:inReplyToAtomUri", +      "conversation": "ostatus:conversation", +      "sensitive": "as:sensitive", +      "toot": "http://joinmastodon.org/ns#", +      "votersCount": "toot:votersCount" +    } +  ], +  "id": "https://example.com/users/{{nickname}}/statuses/{{status_id}}", +  "type": "Note", +  "summary": null, +  "inReplyTo": null, +  "published": "2021-02-24T12:40:49Z", +  "url": "https://example.com/@{{nickname}}/{{status_id}}", +  "attributedTo": "https://example.com/users/{{nickname}}", +  "to": [ +    "https://www.w3.org/ns/activitystreams#Public" +  ], +  "cc": [ +    "https://example.com/users/{{nickname}}/followers" +  ], +  "sensitive": false, +  "atomUri": "https://example.com/users/{{nickname}}/statuses/{{status_id}}", +  "inReplyToAtomUri": null, +  "conversation": "tag:example.com,2021-02-24:objectId=15:objectType=Conversation", +  "content": "<p></p>", +  "contentMap": { +    "en": "<p></p>" +  }, +  "attachment": [], +  "tag": [], +  "replies": { +    "id": "https://example.com/users/{{nickname}}/statuses/{{status_id}}/replies", +    "type": "Collection", +    "first": { +      "type": "CollectionPage", +      "next": "https://example.com/users/{{nickname}}/statuses/{{status_id}}/replies?only_other_accounts=true&page=true", +      "partOf": "https://example.com/users/{{nickname}}/statuses/{{status_id}}/replies", +      "items": [] +    } +  } +} diff --git a/test/fixtures/statuses/note.json b/test/fixtures/statuses/note.json new file mode 100644 index 000000000..41735cbc5 --- /dev/null +++ b/test/fixtures/statuses/note.json @@ -0,0 +1,27 @@ +{ +  "@context": [ +    "https://www.w3.org/ns/activitystreams", +    "https://example.com/schemas/litepub-0.1.jsonld", +    { +      "@language": "und" +    } +  ], +  "actor": "https://example.com/users/{{nickname}}", +  "attachment": [], +  "attributedTo": "https://example.com/users/{{nickname}}", +  "cc": [ +    "https://example.com/users/{{nickname}}/followers" +  ], +  "content": "Content", +  "context": "https://example.com/contexts/e4b180e1-7403-477f-aeb4-de57e7a3fe7f", +  "conversation": "https://example.com/contexts/e4b180e1-7403-477f-aeb4-de57e7a3fe7f", +  "id": "https://example.com/objects/{{object_id}}", +  "published": "2019-12-15T22:00:05.279583Z", +  "sensitive": false, +  "summary": "", +  "tag": [], +  "to": [ +    "https://www.w3.org/ns/activitystreams#Public" +  ], +  "type": "Note" +} diff --git a/test/fixtures/users_mock/masto_featured.json b/test/fixtures/users_mock/masto_featured.json new file mode 100644 index 000000000..646a343ad --- /dev/null +++ b/test/fixtures/users_mock/masto_featured.json @@ -0,0 +1,18 @@ +{ +  "@context": [ +    "https://www.w3.org/ns/activitystreams", +    { +      "ostatus": "http://ostatus.org#", +      "atomUri": "ostatus:atomUri", +      "inReplyToAtomUri": "ostatus:inReplyToAtomUri", +      "conversation": "ostatus:conversation", +      "sensitive": "as:sensitive", +      "toot": "http://joinmastodon.org/ns#", +      "votersCount": "toot:votersCount" +    } +  ], +  "id": "https://{{domain}}/users/{{nickname}}/collections/featured", +  "type": "OrderedCollection", +  "totalItems": 0, +  "orderedItems": [] +} diff --git a/test/fixtures/users_mock/user.json b/test/fixtures/users_mock/user.json new file mode 100644 index 000000000..c722a1145 --- /dev/null +++ b/test/fixtures/users_mock/user.json @@ -0,0 +1,42 @@ +{ +  "@context": [ +    "https://www.w3.org/ns/activitystreams", +    "https://example.com/schemas/litepub-0.1.jsonld", +    { +      "@language": "und" +    } +  ], +  "attachment": [], +  "endpoints": { +    "oauthAuthorizationEndpoint": "https://example.com/oauth/authorize", +    "oauthRegistrationEndpoint": "https://example.com/api/v1/apps", +    "oauthTokenEndpoint": "https://example.com/oauth/token", +    "sharedInbox": "https://example.com/inbox" +  }, +  "followers": "https://example.com/users/{{nickname}}/followers", +  "following": "https://example.com/users/{{nickname}}/following", +  "icon": { +    "type": "Image", +    "url": "https://example.com/media/4e914f5b84e4a259a3f6c2d2edc9ab642f2ab05f3e3d9c52c81fc2d984b3d51e.jpg" +  }, +  "id": "https://example.com/users/{{nickname}}", +  "image": { +    "type": "Image", +    "url": "https://example.com/media/f739efddefeee49c6e67e947c4811fdc911785c16ae43da4c3684051fbf8da6a.jpg?name=f739efddefeee49c6e67e947c4811fdc911785c16ae43da4c3684051fbf8da6a.jpg" +  }, +  "inbox": "https://example.com/users/{{nickname}}/inbox", +  "manuallyApprovesFollowers": false, +  "name": "{{nickname}}", +  "outbox": "https://example.com/users/{{nickname}}/outbox", +  "preferredUsername": "{{nickname}}", +  "publicKey": { +    "id": "https://example.com/users/{{nickname}}#main-key", +    "owner": "https://example.com/users/{{nickname}}", +    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5DLtwGXNZElJyxFGfcVc\nXANhaMadj/iYYQwZjOJTV9QsbtiNBeIK54PJrYuU0/0YIdrvS1iqheX5IwXRhcwa\nhm3ZyLz7XeN9st7FBni4BmZMBtMpxAuYuu5p/jbWy13qAiYOhPreCx0wrWgm/lBD\n9mkgaxIxPooBE0S4ZWEJIDIV1Vft3AWcRUyWW1vIBK0uZzs6GYshbQZB952S0yo4\nFzI1hABGHncH8UvuFauh4EZ8tY7/X5I0pGRnDOcRN1dAht5w5yTA+6r5kebiFQjP\nIzN/eCO/a9Flrj9YGW7HDNtjSOH0A31PLRGlJtJO3yK57dnf5ppyCZGfL4emShQo\ncQIDAQAB\n-----END PUBLIC KEY-----\n\n" +  }, +  "featured": "https://example.com/users/{{nickname}}/collections/featured", +  "summary": "your friendly neighborhood pleroma developer<br>I like cute things and distributed systems, and really hate delete and redrafts", +  "tag": [], +  "type": "Person", +  "url": "https://example.com/users/{{nickname}}" +} diff --git a/test/pleroma/activity_test.exs b/test/pleroma/activity_test.exs index 390a06344..962bc7e45 100644 --- a/test/pleroma/activity_test.exs +++ b/test/pleroma/activity_test.exs @@ -254,4 +254,26 @@ defmodule Pleroma.ActivityTest do      assert %{id: ^id} = Activity.get_by_object_ap_id_with_object(obj_id)    end + +  test "add_by_params_query/3" do +    user = insert(:user) + +    note = insert(:note_activity, user: user) + +    insert(:add_activity, user: user, note: note) +    insert(:add_activity, user: user, note: note) +    insert(:add_activity, user: user) + +    assert Repo.aggregate(Activity, :count, :id) == 4 + +    add_query = +      Activity.add_by_params_query(note.data["object"], user.ap_id, user.featured_address) + +    assert Repo.aggregate(add_query, :count, :id) == 2 + +    Repo.delete_all(add_query) +    assert Repo.aggregate(add_query, :count, :id) == 0 + +    assert Repo.aggregate(Activity, :count, :id) == 2 +  end  end diff --git a/test/pleroma/config/release_runtime_provider_test.exs b/test/pleroma/config/release_runtime_provider_test.exs new file mode 100644 index 000000000..6578d3268 --- /dev/null +++ b/test/pleroma/config/release_runtime_provider_test.exs @@ -0,0 +1,45 @@ +defmodule Pleroma.Config.ReleaseRuntimeProviderTest do +  use ExUnit.Case, async: true + +  alias Pleroma.Config.ReleaseRuntimeProvider + +  describe "load/2" do +    test "loads release defaults config and warns about non-existent runtime config" do +      ExUnit.CaptureIO.capture_io(fn -> +        merged = ReleaseRuntimeProvider.load([], []) +        assert merged == Pleroma.Config.Holder.release_defaults() +      end) =~ +        "!!! Config path is not declared! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file" +    end + +    test "merged runtime config" do +      merged = +        ReleaseRuntimeProvider.load([], config_path: "test/fixtures/config/temp.secret.exs") + +      assert merged[:pleroma][:first_setting] == [key: "value", key2: [Pleroma.Repo]] +      assert merged[:pleroma][:second_setting] == [key: "value2", key2: ["Activity"]] +    end + +    test "merged exported config" do +      ExUnit.CaptureIO.capture_io(fn -> +        merged = +          ReleaseRuntimeProvider.load([], +            exported_config_path: "test/fixtures/config/temp.exported_from_db.secret.exs" +          ) + +        assert merged[:pleroma][:exported_config_merged] +      end) =~ +        "!!! Config path is not declared! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file" +    end + +    test "runtime config is merged with exported config" do +      merged = +        ReleaseRuntimeProvider.load([], +          config_path: "test/fixtures/config/temp.secret.exs", +          exported_config_path: "test/fixtures/config/temp.exported_from_db.secret.exs" +        ) + +      assert merged[:pleroma][:first_setting] == [key2: [Pleroma.Repo], key: "new value"] +    end +  end +end diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index 6f5bcab57..d81c1b8eb 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -2338,4 +2338,49 @@ defmodule Pleroma.UserTest do      assert User.active_user_count(6) == 3      assert User.active_user_count(1) == 1    end + +  describe "pins" do +    setup do +      user = insert(:user) + +      [user: user, object_id: object_id_from_created_activity(user)] +    end + +    test "unique pins", %{user: user, object_id: object_id} do +      assert {:ok, %{pinned_objects: %{^object_id => pinned_at1} = pins} = updated_user} = +               User.add_pinned_object_id(user, object_id) + +      assert Enum.count(pins) == 1 + +      assert {:ok, %{pinned_objects: %{^object_id => pinned_at2} = pins}} = +               User.add_pinned_object_id(updated_user, object_id) + +      assert pinned_at1 == pinned_at2 + +      assert Enum.count(pins) == 1 +    end + +    test "respects max_pinned_statuses limit", %{user: user, object_id: object_id} do +      clear_config([:instance, :max_pinned_statuses], 1) +      {:ok, updated} = User.add_pinned_object_id(user, object_id) + +      object_id2 = object_id_from_created_activity(user) + +      {:error, %{errors: errors}} = User.add_pinned_object_id(updated, object_id2) +      assert Keyword.has_key?(errors, :pinned_objects) +    end + +    test "remove_pinned_object_id/2", %{user: user, object_id: object_id} do +      assert {:ok, updated} = User.add_pinned_object_id(user, object_id) + +      {:ok, after_remove} = User.remove_pinned_object_id(updated, object_id) +      assert after_remove.pinned_objects == %{} +    end +  end + +  defp object_id_from_created_activity(user) do +    %{id: id} = insert(:note_activity, user: user) +    %{object: %{data: %{"id" => object_id}}} = Activity.get_by_id_with_object(id) +    object_id +  end  end diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs index 19e04d472..cea4b3a97 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -636,6 +636,186 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        |> post("/inbox", non_create_data)        |> json_response(400)      end + +    test "accepts Add/Remove activities", %{conn: conn} do +      object_id = "c61d6733-e256-4fe1-ab13-1e369789423f" + +      status = +        File.read!("test/fixtures/statuses/note.json") +        |> String.replace("{{nickname}}", "lain") +        |> String.replace("{{object_id}}", object_id) + +      object_url = "https://example.com/objects/#{object_id}" + +      user = +        File.read!("test/fixtures/users_mock/user.json") +        |> String.replace("{{nickname}}", "lain") + +      actor = "https://example.com/users/lain" + +      Tesla.Mock.mock(fn +        %{ +          method: :get, +          url: ^object_url +        } -> +          %Tesla.Env{ +            status: 200, +            body: status, +            headers: [{"content-type", "application/activity+json"}] +          } + +        %{ +          method: :get, +          url: ^actor +        } -> +          %Tesla.Env{ +            status: 200, +            body: user, +            headers: [{"content-type", "application/activity+json"}] +          } + +        %{method: :get, url: "https://example.com/users/lain/collections/featured"} -> +          %Tesla.Env{ +            status: 200, +            body: +              "test/fixtures/users_mock/masto_featured.json" +              |> File.read!() +              |> String.replace("{{domain}}", "example.com") +              |> String.replace("{{nickname}}", "lain"), +            headers: [{"content-type", "application/activity+json"}] +          } +      end) + +      data = %{ +        "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423f", +        "actor" => actor, +        "object" => object_url, +        "target" => "https://example.com/users/lain/collections/featured", +        "type" => "Add", +        "to" => [Pleroma.Constants.as_public()] +      } + +      assert "ok" == +               conn +               |> assign(:valid_signature, true) +               |> put_req_header("content-type", "application/activity+json") +               |> post("/inbox", data) +               |> json_response(200) + +      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) +      assert Activity.get_by_ap_id(data["id"]) +      user = User.get_cached_by_ap_id(data["actor"]) +      assert user.pinned_objects[data["object"]] + +      data = %{ +        "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423d", +        "actor" => actor, +        "object" => object_url, +        "target" => "https://example.com/users/lain/collections/featured", +        "type" => "Remove", +        "to" => [Pleroma.Constants.as_public()] +      } + +      assert "ok" == +               conn +               |> assign(:valid_signature, true) +               |> put_req_header("content-type", "application/activity+json") +               |> post("/inbox", data) +               |> json_response(200) + +      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) +      user = refresh_record(user) +      refute user.pinned_objects[data["object"]] +    end + +    test "mastodon pin/unpin", %{conn: conn} do +      status_id = "105786274556060421" + +      status = +        File.read!("test/fixtures/statuses/masto-note.json") +        |> String.replace("{{nickname}}", "lain") +        |> String.replace("{{status_id}}", status_id) + +      status_url = "https://example.com/users/lain/statuses/#{status_id}" + +      user = +        File.read!("test/fixtures/users_mock/user.json") +        |> String.replace("{{nickname}}", "lain") + +      actor = "https://example.com/users/lain" + +      Tesla.Mock.mock(fn +        %{ +          method: :get, +          url: ^status_url +        } -> +          %Tesla.Env{ +            status: 200, +            body: status, +            headers: [{"content-type", "application/activity+json"}] +          } + +        %{ +          method: :get, +          url: ^actor +        } -> +          %Tesla.Env{ +            status: 200, +            body: user, +            headers: [{"content-type", "application/activity+json"}] +          } + +        %{method: :get, url: "https://example.com/users/lain/collections/featured"} -> +          %Tesla.Env{ +            status: 200, +            body: +              "test/fixtures/users_mock/masto_featured.json" +              |> File.read!() +              |> String.replace("{{domain}}", "example.com") +              |> String.replace("{{nickname}}", "lain"), +            headers: [{"content-type", "application/activity+json"}] +          } +      end) + +      data = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "actor" => actor, +        "object" => status_url, +        "target" => "https://example.com/users/lain/collections/featured", +        "type" => "Add" +      } + +      assert "ok" == +               conn +               |> assign(:valid_signature, true) +               |> put_req_header("content-type", "application/activity+json") +               |> post("/inbox", data) +               |> json_response(200) + +      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) +      assert Activity.get_by_object_ap_id_with_object(data["object"]) +      user = User.get_cached_by_ap_id(data["actor"]) +      assert user.pinned_objects[data["object"]] + +      data = %{ +        "actor" => actor, +        "object" => status_url, +        "target" => "https://example.com/users/lain/collections/featured", +        "type" => "Remove" +      } + +      assert "ok" == +               conn +               |> assign(:valid_signature, true) +               |> put_req_header("content-type", "application/activity+json") +               |> post("/inbox", data) +               |> json_response(200) + +      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) +      assert Activity.get_by_object_ap_id_with_object(data["object"]) +      user = refresh_record(user) +      refute user.pinned_objects[data["object"]] +    end    end    describe "/users/:nickname/inbox" do @@ -1772,4 +1952,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        |> json_response(403)      end    end + +  test "pinned collection", %{conn: conn} do +    clear_config([:instance, :max_pinned_statuses], 2) +    user = insert(:user) +    objects = insert_list(2, :note, user: user) + +    Enum.reduce(objects, user, fn %{data: %{"id" => object_id}}, user -> +      {:ok, updated} = User.add_pinned_object_id(user, object_id) +      updated +    end) + +    %{nickname: nickname, featured_address: featured_address, pinned_objects: pinned_objects} = +      refresh_record(user) + +    %{"id" => ^featured_address, "orderedItems" => items} = +      conn +      |> get("/users/#{nickname}/collections/featured") +      |> json_response(200) + +    object_ids = Enum.map(items, & &1["id"]) + +    assert Enum.all?(pinned_objects, fn {obj_id, _} -> +             obj_id in object_ids +           end) +  end  end diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index c7fa452f7..64e12066e 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -235,6 +235,83 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do                 "url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}]               }      end + +    test "fetches user featured collection" do +      ap_id = "https://example.com/users/lain" + +      featured_url = "https://example.com/users/lain/collections/featured" + +      user_data = +        "test/fixtures/users_mock/user.json" +        |> File.read!() +        |> String.replace("{{nickname}}", "lain") +        |> Jason.decode!() +        |> Map.put("featured", featured_url) +        |> Jason.encode!() + +      object_id = Ecto.UUID.generate() + +      featured_data = +        "test/fixtures/mastodon/collections/featured.json" +        |> File.read!() +        |> String.replace("{{domain}}", "example.com") +        |> String.replace("{{nickname}}", "lain") +        |> String.replace("{{object_id}}", object_id) + +      object_url = "https://example.com/objects/#{object_id}" + +      object_data = +        "test/fixtures/statuses/note.json" +        |> File.read!() +        |> String.replace("{{object_id}}", object_id) +        |> String.replace("{{nickname}}", "lain") + +      Tesla.Mock.mock(fn +        %{ +          method: :get, +          url: ^ap_id +        } -> +          %Tesla.Env{ +            status: 200, +            body: user_data, +            headers: [{"content-type", "application/activity+json"}] +          } + +        %{ +          method: :get, +          url: ^featured_url +        } -> +          %Tesla.Env{ +            status: 200, +            body: featured_data, +            headers: [{"content-type", "application/activity+json"}] +          } +      end) + +      Tesla.Mock.mock_global(fn +        %{ +          method: :get, +          url: ^object_url +        } -> +          %Tesla.Env{ +            status: 200, +            body: object_data, +            headers: [{"content-type", "application/activity+json"}] +          } +      end) + +      {:ok, user} = ActivityPub.make_user_from_ap_id(ap_id) +      Process.sleep(50) + +      assert user.featured_address == featured_url +      assert Map.has_key?(user.pinned_objects, object_url) + +      in_db = Pleroma.User.get_by_ap_id(ap_id) +      assert in_db.featured_address == featured_url +      assert Map.has_key?(user.pinned_objects, object_url) + +      assert %{data: %{"id" => ^object_url}} = Object.get_by_ap_id(object_url) +    end    end    test "it fetches the appropriate tag-restricted posts" do diff --git a/test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs b/test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs new file mode 100644 index 000000000..a61562558 --- /dev/null +++ b/test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs @@ -0,0 +1,126 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicyTest do +  use Pleroma.DataCase, async: true + +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.MRF.FollowBotPolicy + +  import Pleroma.Factory + +  describe "FollowBotPolicy" do +    test "follows remote users" do +      bot = insert(:user, actor_type: "Service") +      remote_user = insert(:user, local: false) +      clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname) + +      message = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "to" => [remote_user.follower_address], +        "cc" => ["https://www.w3.org/ns/activitystreams#Public"], +        "type" => "Create", +        "object" => %{ +          "content" => "Test post", +          "type" => "Note", +          "attributedTo" => remote_user.ap_id, +          "inReplyTo" => nil +        }, +        "actor" => remote_user.ap_id +      } + +      refute User.following?(bot, remote_user) + +      assert User.get_follow_requests(remote_user) |> length == 0 + +      FollowBotPolicy.filter(message) + +      assert User.get_follow_requests(remote_user) |> length == 1 +    end + +    test "does not follow users with #nobot in bio" do +      bot = insert(:user, actor_type: "Service") +      remote_user = insert(:user, %{local: false, bio: "go away bots! #nobot"}) +      clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname) + +      message = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "to" => [remote_user.follower_address], +        "cc" => ["https://www.w3.org/ns/activitystreams#Public"], +        "type" => "Create", +        "object" => %{ +          "content" => "I don't like follow bots", +          "type" => "Note", +          "attributedTo" => remote_user.ap_id, +          "inReplyTo" => nil +        }, +        "actor" => remote_user.ap_id +      } + +      refute User.following?(bot, remote_user) + +      assert User.get_follow_requests(remote_user) |> length == 0 + +      FollowBotPolicy.filter(message) + +      assert User.get_follow_requests(remote_user) |> length == 0 +    end + +    test "does not follow local users" do +      bot = insert(:user, actor_type: "Service") +      local_user = insert(:user, local: true) +      clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname) + +      message = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "to" => [local_user.follower_address], +        "cc" => ["https://www.w3.org/ns/activitystreams#Public"], +        "type" => "Create", +        "object" => %{ +          "content" => "Hi I'm a local user", +          "type" => "Note", +          "attributedTo" => local_user.ap_id, +          "inReplyTo" => nil +        }, +        "actor" => local_user.ap_id +      } + +      refute User.following?(bot, local_user) + +      assert User.get_follow_requests(local_user) |> length == 0 + +      FollowBotPolicy.filter(message) + +      assert User.get_follow_requests(local_user) |> length == 0 +    end + +    test "does not follow users requiring follower approval" do +      bot = insert(:user, actor_type: "Service") +      remote_user = insert(:user, %{local: false, is_locked: true}) +      clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname) + +      message = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "to" => [remote_user.follower_address], +        "cc" => ["https://www.w3.org/ns/activitystreams#Public"], +        "type" => "Create", +        "object" => %{ +          "content" => "I don't like randos following me", +          "type" => "Note", +          "attributedTo" => remote_user.ap_id, +          "inReplyTo" => nil +        }, +        "actor" => remote_user.ap_id +      } + +      refute User.following?(bot, remote_user) + +      assert User.get_follow_requests(remote_user) |> length == 0 + +      FollowBotPolicy.filter(message) + +      assert User.get_follow_requests(remote_user) |> length == 0 +    end +  end +end diff --git a/test/pleroma/web/activity_pub/pipeline_test.exs b/test/pleroma/web/activity_pub/pipeline_test.exs index 52fa933ee..e606fa3d1 100644 --- a/test/pleroma/web/activity_pub/pipeline_test.exs +++ b/test/pleroma/web/activity_pub/pipeline_test.exs @@ -25,9 +25,6 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do        MRFMock        |> expect(:pipeline_filter, fn o, m -> {:ok, o, m} end) -      ActivityPubMock -      |> expect(:persist, fn o, m -> {:ok, o, m} end) -        SideEffectsMock        |> expect(:handle, fn o, m -> {:ok, o, m} end)        |> expect(:handle_after_transaction, fn m -> m end) @@ -42,6 +39,9 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do        activity_with_object = %{activity | data: Map.put(activity.data, "object", object)} +      ActivityPubMock +      |> expect(:persist, fn _, m -> {:ok, activity, m} end) +        FederatorMock        |> expect(:publish, fn ^activity_with_object -> :ok end) @@ -50,7 +50,7 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do        assert {:ok, ^activity, ^meta} =                 Pleroma.Web.ActivityPub.Pipeline.common_pipeline( -                 activity, +                 activity.data,                   meta                 )      end @@ -59,6 +59,9 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do        activity = insert(:note_activity)        meta = [local: true] +      ActivityPubMock +      |> expect(:persist, fn _, m -> {:ok, activity, m} end) +        FederatorMock        |> expect(:publish, fn ^activity -> :ok end) @@ -66,29 +69,35 @@ defmodule Pleroma.Web.ActivityPub.PipelineTest do        |> expect(:get, fn [:instance, :federating] -> true end)        assert {:ok, ^activity, ^meta} = -               Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta) +               Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity.data, meta)      end      test "it goes through validation, filtering, persisting, side effects without federation for remote activities" do        activity = insert(:note_activity)        meta = [local: false] +      ActivityPubMock +      |> expect(:persist, fn _, m -> {:ok, activity, m} end) +        ConfigMock        |> expect(:get, fn [:instance, :federating] -> true end)        assert {:ok, ^activity, ^meta} = -               Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta) +               Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity.data, meta)      end      test "it goes through validation, filtering, persisting, side effects without federation for local activities if federation is deactivated" do        activity = insert(:note_activity)        meta = [local: true] +      ActivityPubMock +      |> expect(:persist, fn _, m -> {:ok, activity, m} end) +        ConfigMock        |> expect(:get, fn [:instance, :federating] -> false end)        assert {:ok, ^activity, ^meta} = -               Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta) +               Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity.data, meta)      end    end  end diff --git a/test/pleroma/web/activity_pub/transmogrifier/add_remove_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/add_remove_handling_test.exs new file mode 100644 index 000000000..fc7757125 --- /dev/null +++ b/test/pleroma/web/activity_pub/transmogrifier/add_remove_handling_test.exs @@ -0,0 +1,172 @@ +defmodule Pleroma.Web.ActivityPub.Transmogrifier.AddRemoveHandlingTest do +  use Oban.Testing, repo: Pleroma.Repo +  use Pleroma.DataCase, async: true + +  require Pleroma.Constants + +  import Pleroma.Factory + +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.Transmogrifier + +  test "it accepts Add/Remove activities" do +    user = +      "test/fixtures/users_mock/user.json" +      |> File.read!() +      |> String.replace("{{nickname}}", "lain") + +    object_id = "c61d6733-e256-4fe1-ab13-1e369789423f" + +    object = +      "test/fixtures/statuses/note.json" +      |> File.read!() +      |> String.replace("{{nickname}}", "lain") +      |> String.replace("{{object_id}}", object_id) + +    object_url = "https://example.com/objects/#{object_id}" + +    actor = "https://example.com/users/lain" + +    Tesla.Mock.mock(fn +      %{ +        method: :get, +        url: ^actor +      } -> +        %Tesla.Env{ +          status: 200, +          body: user, +          headers: [{"content-type", "application/activity+json"}] +        } + +      %{ +        method: :get, +        url: ^object_url +      } -> +        %Tesla.Env{ +          status: 200, +          body: object, +          headers: [{"content-type", "application/activity+json"}] +        } + +      %{method: :get, url: "https://example.com/users/lain/collections/featured"} -> +        %Tesla.Env{ +          status: 200, +          body: +            "test/fixtures/users_mock/masto_featured.json" +            |> File.read!() +            |> String.replace("{{domain}}", "example.com") +            |> String.replace("{{nickname}}", "lain"), +          headers: [{"content-type", "application/activity+json"}] +        } +    end) + +    message = %{ +      "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423f", +      "actor" => actor, +      "object" => object_url, +      "target" => "https://example.com/users/lain/collections/featured", +      "type" => "Add", +      "to" => [Pleroma.Constants.as_public()], +      "cc" => ["https://example.com/users/lain/followers"] +    } + +    assert {:ok, activity} = Transmogrifier.handle_incoming(message) +    assert activity.data == message +    user = User.get_cached_by_ap_id(actor) +    assert user.pinned_objects[object_url] + +    remove = %{ +      "id" => "http://localhost:400/objects/d61d6733-e256-4fe1-ab13-1e369789423d", +      "actor" => actor, +      "object" => object_url, +      "target" => "https://example.com/users/lain/collections/featured", +      "type" => "Remove", +      "to" => [Pleroma.Constants.as_public()], +      "cc" => ["https://example.com/users/lain/followers"] +    } + +    assert {:ok, activity} = Transmogrifier.handle_incoming(remove) +    assert activity.data == remove + +    user = refresh_record(user) +    refute user.pinned_objects[object_url] +  end + +  test "Add/Remove activities for remote users without featured address" do +    user = insert(:user, local: false, domain: "example.com") + +    user = +      user +      |> Ecto.Changeset.change(featured_address: nil) +      |> Repo.update!() + +    %{host: host} = URI.parse(user.ap_id) + +    user_data = +      "test/fixtures/users_mock/user.json" +      |> File.read!() +      |> String.replace("{{nickname}}", user.nickname) + +    object_id = "c61d6733-e256-4fe1-ab13-1e369789423f" + +    object = +      "test/fixtures/statuses/note.json" +      |> File.read!() +      |> String.replace("{{nickname}}", user.nickname) +      |> String.replace("{{object_id}}", object_id) + +    object_url = "https://#{host}/objects/#{object_id}" + +    actor = "https://#{host}/users/#{user.nickname}" + +    featured = "https://#{host}/users/#{user.nickname}/collections/featured" + +    Tesla.Mock.mock(fn +      %{ +        method: :get, +        url: ^actor +      } -> +        %Tesla.Env{ +          status: 200, +          body: user_data, +          headers: [{"content-type", "application/activity+json"}] +        } + +      %{ +        method: :get, +        url: ^object_url +      } -> +        %Tesla.Env{ +          status: 200, +          body: object, +          headers: [{"content-type", "application/activity+json"}] +        } + +      %{method: :get, url: ^featured} -> +        %Tesla.Env{ +          status: 200, +          body: +            "test/fixtures/users_mock/masto_featured.json" +            |> File.read!() +            |> String.replace("{{domain}}", "#{host}") +            |> String.replace("{{nickname}}", user.nickname), +          headers: [{"content-type", "application/activity+json"}] +        } +    end) + +    message = %{ +      "id" => "https://#{host}/objects/d61d6733-e256-4fe1-ab13-1e369789423f", +      "actor" => actor, +      "object" => object_url, +      "target" => "https://#{host}/users/#{user.nickname}/collections/featured", +      "type" => "Add", +      "to" => [Pleroma.Constants.as_public()], +      "cc" => ["https://#{host}/users/#{user.nickname}/followers"] +    } + +    assert {:ok, activity} = Transmogrifier.handle_incoming(message) +    assert activity.data == message +    user = User.get_cached_by_ap_id(actor) +    assert user.pinned_objects[object_url] +  end +end diff --git a/test/pleroma/web/admin_api/controllers/config_controller_test.exs b/test/pleroma/web/admin_api/controllers/config_controller_test.exs index 578a4c914..c39c1b1e1 100644 --- a/test/pleroma/web/admin_api/controllers/config_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/config_controller_test.exs @@ -1410,6 +1410,82 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do                 "need_reboot" => false               }      end + +    test "custom instance thumbnail", %{conn: conn} do +      clear_config([:instance]) + +      params = %{ +        "group" => ":pleroma", +        "key" => ":instance", +        "value" => [ +          %{ +            "tuple" => [ +              ":instance_thumbnail", +              "https://example.com/media/new_thumbnail.jpg" +            ] +          } +        ] +      } + +      res = +        assert conn +               |> put_req_header("content-type", "application/json") +               |> post("/api/pleroma/admin/config", %{"configs" => [params]}) +               |> json_response_and_validate_schema(200) + +      assert res == %{ +               "configs" => [ +                 %{ +                   "db" => [":instance_thumbnail"], +                   "group" => ":pleroma", +                   "key" => ":instance", +                   "value" => params["value"] +                 } +               ], +               "need_reboot" => false +             } + +      _res = +        assert conn +               |> get("/api/v1/instance") +               |> json_response_and_validate_schema(200) + +      assert res = %{"thumbnail" => "https://example.com/media/new_thumbnail.jpg"} +    end + +    test "Concurrent Limiter", %{conn: conn} do +      clear_config([ConcurrentLimiter]) + +      params = %{ +        "group" => ":pleroma", +        "key" => "ConcurrentLimiter", +        "value" => [ +          %{ +            "tuple" => [ +              "Pleroma.Web.RichMedia.Helpers", +              [ +                %{"tuple" => [":max_running", 6]}, +                %{"tuple" => [":max_waiting", 6]} +              ] +            ] +          }, +          %{ +            "tuple" => [ +              "Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy", +              [ +                %{"tuple" => [":max_running", 7]}, +                %{"tuple" => [":max_waiting", 7]} +              ] +            ] +          } +        ] +      } + +      assert conn +             |> put_req_header("content-type", "application/json") +             |> post("/api/pleroma/admin/config", %{"configs" => [params]}) +             |> json_response_and_validate_schema(200) +    end    end    describe "GET /api/pleroma/admin/config/descriptions" do diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index 6619f8fc8..fa55c2832 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -827,13 +827,17 @@ defmodule Pleroma.Web.CommonAPITest do        [user: user, activity: activity]      end +    test "activity not found error", %{user: user} do +      assert {:error, :not_found} = CommonAPI.pin("id", user) +    end +      test "pin status", %{user: user, activity: activity} do        assert {:ok, ^activity} = CommonAPI.pin(activity.id, user) -      id = activity.id +      %{data: %{"id" => object_id}} = Object.normalize(activity)        user = refresh_record(user) -      assert %User{pinned_activities: [^id]} = user +      assert user.pinned_objects |> Map.keys() == [object_id]      end      test "pin poll", %{user: user} do @@ -845,10 +849,11 @@ defmodule Pleroma.Web.CommonAPITest do        assert {:ok, ^activity} = CommonAPI.pin(activity.id, user) -      id = activity.id +      %{data: %{"id" => object_id}} = Object.normalize(activity) +        user = refresh_record(user) -      assert %User{pinned_activities: [^id]} = user +      assert user.pinned_objects |> Map.keys() == [object_id]      end      test "unlisted statuses can be pinned", %{user: user} do @@ -859,7 +864,7 @@ defmodule Pleroma.Web.CommonAPITest do      test "only self-authored can be pinned", %{activity: activity} do        user = insert(:user) -      assert {:error, "Could not pin"} = CommonAPI.pin(activity.id, user) +      assert {:error, :ownership_error} = CommonAPI.pin(activity.id, user)      end      test "max pinned statuses", %{user: user, activity: activity_one} do @@ -869,8 +874,12 @@ defmodule Pleroma.Web.CommonAPITest do        user = refresh_record(user) -      assert {:error, "You have already pinned the maximum number of statuses"} = -               CommonAPI.pin(activity_two.id, user) +      assert {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity_two.id, user) +    end + +    test "only public can be pinned", %{user: user} do +      {:ok, activity} = CommonAPI.post(user, %{status: "private status", visibility: "private"}) +      {:error, :visibility_error} = CommonAPI.pin(activity.id, user)      end      test "unpin status", %{user: user, activity: activity} do @@ -884,7 +893,7 @@ defmodule Pleroma.Web.CommonAPITest do        user = refresh_record(user) -      assert %User{pinned_activities: []} = user +      assert user.pinned_objects == %{}      end      test "should unpin when deleting a status", %{user: user, activity: activity} do @@ -896,7 +905,40 @@ defmodule Pleroma.Web.CommonAPITest do        user = refresh_record(user) -      assert %User{pinned_activities: []} = user +      assert user.pinned_objects == %{} +    end + +    test "ephemeral activity won't be deleted if was pinned", %{user: user} do +      {:ok, activity} = CommonAPI.post(user, %{status: "Hello!", expires_in: 601}) + +      assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id) + +      {:ok, _activity} = CommonAPI.pin(activity.id, user) +      refute Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id) + +      user = refresh_record(user) +      {:ok, _} = CommonAPI.unpin(activity.id, user) + +      # recreates expiration job on unpin +      assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id) +    end + +    test "ephemeral activity deletion job won't be deleted on pinning error", %{ +      user: user, +      activity: activity +    } do +      clear_config([:instance, :max_pinned_statuses], 1) + +      {:ok, _activity} = CommonAPI.pin(activity.id, user) + +      {:ok, activity2} = CommonAPI.post(user, %{status: "another status", expires_in: 601}) + +      assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id) + +      user = refresh_record(user) +      {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity2.id, user) + +      assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)      end    end diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index f616f405e..99ad87d05 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -1209,20 +1209,27 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do      setup do: clear_config([:instance, :max_pinned_statuses], 1)      test "pin status", %{conn: conn, user: user, activity: activity} do -      id_str = to_string(activity.id) +      id = activity.id -      assert %{"id" => ^id_str, "pinned" => true} = +      assert %{"id" => ^id, "pinned" => true} =                 conn                 |> put_req_header("content-type", "application/json")                 |> post("/api/v1/statuses/#{activity.id}/pin")                 |> json_response_and_validate_schema(200) -      assert [%{"id" => ^id_str, "pinned" => true}] = +      assert [%{"id" => ^id, "pinned" => true}] =                 conn                 |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")                 |> json_response_and_validate_schema(200)      end +    test "non authenticated user", %{activity: activity} do +      assert build_conn() +             |> put_req_header("content-type", "application/json") +             |> post("/api/v1/statuses/#{activity.id}/pin") +             |> json_response(403) == %{"error" => "Invalid credentials."} +    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"}) @@ -1231,7 +1238,18 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do          |> put_req_header("content-type", "application/json")          |> post("/api/v1/statuses/#{dm.id}/pin") -      assert json_response_and_validate_schema(conn, 400) == %{"error" => "Could not pin"} +      assert json_response_and_validate_schema(conn, 422) == %{ +               "error" => "Non-public status cannot be pinned" +             } +    end + +    test "pin by another user", %{activity: activity} do +      %{conn: conn} = oauth_access(["write:accounts"]) + +      assert conn +             |> put_req_header("content-type", "application/json") +             |> post("/api/v1/statuses/#{activity.id}/pin") +             |> json_response(422) == %{"error" => "Someone else's status cannot be pinned"}      end      test "unpin status", %{conn: conn, user: user, activity: activity} do @@ -1252,13 +1270,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do                 |> json_response_and_validate_schema(200)      end -    test "/unpin: returns 400 error when activity is not exist", %{conn: conn} do -      conn = -        conn -        |> put_req_header("content-type", "application/json") -        |> post("/api/v1/statuses/1/unpin") - -      assert json_response_and_validate_schema(conn, 400) == %{"error" => "Could not unpin"} +    test "/unpin: returns 404 error when activity doesn't exist", %{conn: conn} do +      assert conn +             |> put_req_header("content-type", "application/json") +             |> post("/api/v1/statuses/1/unpin") +             |> json_response_and_validate_schema(404) == %{"error" => "Record not found"}      end      test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index 4172cc294..fbea25079 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -286,7 +286,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do          direct_conversation_id: nil,          thread_muted: false,          emoji_reactions: [], -        parent_visible: false +        parent_visible: false, +        pinned_at: nil        }      } diff --git a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs index f389c272b..fa3b29006 100644 --- a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs +++ b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs @@ -27,6 +27,16 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do              body: File.read!("test/fixtures/tesla_mock/status.emelie.json")            } +        %{method: :get, url: "https://mastodon.social/users/emelie/collections/featured"} -> +          %Tesla.Env{ +            status: 200, +            headers: [{"content-type", "application/activity+json"}], +            body: +              File.read!("test/fixtures/users_mock/masto_featured.json") +              |> String.replace("{{domain}}", "mastodon.social") +              |> String.replace("{{nickname}}", "emelie") +          } +          %{method: :get, url: "https://mastodon.social/users/emelie"} ->            %Tesla.Env{              status: 200, @@ -52,6 +62,16 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do              headers: [{"content-type", "application/activity+json"}],              body: File.read!("test/fixtures/tesla_mock/emelie.json")            } + +        %{method: :get, url: "https://mastodon.social/users/emelie/collections/featured"} -> +          %Tesla.Env{ +            status: 200, +            headers: [{"content-type", "application/activity+json"}], +            body: +              File.read!("test/fixtures/users_mock/masto_featured.json") +              |> String.replace("{{domain}}", "mastodon.social") +              |> String.replace("{{nickname}}", "emelie") +          }        end)        response = @@ -70,6 +90,16 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do              headers: [{"content-type", "application/activity+json"}],              body: File.read!("test/fixtures/tesla_mock/emelie.json")            } + +        %{method: :get, url: "https://mastodon.social/users/emelie/collections/featured"} -> +          %Tesla.Env{ +            status: 200, +            headers: [{"content-type", "application/activity+json"}], +            body: +              File.read!("test/fixtures/users_mock/masto_featured.json") +              |> String.replace("{{domain}}", "mastodon.social") +              |> String.replace("{{nickname}}", "emelie") +          }        end)        user = insert(:user) diff --git a/test/support/factory.ex b/test/support/factory.ex index af4fff45b..5c4e65c81 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -4,6 +4,9 @@  defmodule Pleroma.Factory do    use ExMachina.Ecto, repo: Pleroma.Repo + +  require Pleroma.Constants +    alias Pleroma.Object    alias Pleroma.User @@ -41,23 +44,27 @@ defmodule Pleroma.Factory do      urls =        if attrs[:local] == false do -        base_domain = Enum.random(["domain1.com", "domain2.com", "domain3.com"]) +        base_domain = attrs[:domain] || Enum.random(["domain1.com", "domain2.com", "domain3.com"])          ap_id = "https://#{base_domain}/users/#{user.nickname}"          %{            ap_id: ap_id,            follower_address: ap_id <> "/followers", -          following_address: ap_id <> "/following" +          following_address: ap_id <> "/following", +          featured_address: ap_id <> "/collections/featured"          }        else          %{            ap_id: User.ap_id(user),            follower_address: User.ap_followers(user), -          following_address: User.ap_following(user) +          following_address: User.ap_following(user), +          featured_address: User.ap_featured_collection(user)          }        end +    attrs = Map.delete(attrs, :domain) +      user      |> Map.put(:raw_bio, user.bio)      |> Map.merge(urls) @@ -221,6 +228,45 @@ defmodule Pleroma.Factory do      }    end +  def add_activity_factory(attrs \\ %{}) do +    featured_collection_activity(attrs, "Add") +  end + +  def remove_activity_factor(attrs \\ %{}) do +    featured_collection_activity(attrs, "Remove") +  end + +  defp featured_collection_activity(attrs, type) do +    user = attrs[:user] || insert(:user) +    note = attrs[:note] || insert(:note, user: user) + +    data_attrs = +      attrs +      |> Map.get(:data_attrs, %{}) +      |> Map.put(:type, type) + +    attrs = Map.drop(attrs, [:user, :note, :data_attrs]) + +    data = +      %{ +        "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(), +        "target" => user.featured_address, +        "object" => note.data["object"], +        "actor" => note.data["actor"], +        "type" => "Add", +        "to" => [Pleroma.Constants.as_public()], +        "cc" => [user.follower_address] +      } +      |> Map.merge(data_attrs) + +    %Pleroma.Activity{ +      data: data, +      actor: data["actor"], +      recipients: data["to"] +    } +    |> Map.merge(attrs) +  end +    def note_activity_factory(attrs \\ %{}) do      user = attrs[:user] || insert(:user)      note = attrs[:note] || insert(:note, user: user) diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index eb692fab5..8807c2d14 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -89,6 +89,18 @@ defmodule HttpRequestMock do       }}    end +  def get("https://mastodon.sdf.org/users/rinpatch/collections/featured", _, _, _) do +    {:ok, +     %Tesla.Env{ +       status: 200, +       body: +         File.read!("test/fixtures/users_mock/masto_featured.json") +         |> String.replace("{{domain}}", "mastodon.sdf.org") +         |> String.replace("{{nickname}}", "rinpatch"), +       headers: [{"content-type", "application/activity+json"}] +     }} +  end +    def get("https://patch.cx/objects/tesla_mock/poll_attachment", _, _, _) do      {:ok,       %Tesla.Env{ @@ -905,6 +917,18 @@ defmodule HttpRequestMock do       }}    end +  def get("https://mastodon.social/users/lambadalambda/collections/featured", _, _, _) do +    {:ok, +     %Tesla.Env{ +       status: 200, +       body: +         File.read!("test/fixtures/users_mock/masto_featured.json") +         |> String.replace("{{domain}}", "mastodon.social") +         |> String.replace("{{nickname}}", "lambadalambda"), +       headers: activitypub_object_headers() +     }} +  end +    def get("https://apfed.club/channel/indio", _, _, _) do      {:ok,       %Tesla.Env{ | 
