Merge branch 'feature/move-activity' into 'develop'
authorlain <lain@soykaf.club>
Mon, 2 Dec 2019 16:26:19 +0000 (16:26 +0000)
committerlain <lain@soykaf.club>
Mon, 2 Dec 2019 16:26:19 +0000 (16:26 +0000)
Support "Move" activity

Closes #1316

See merge request pleroma/pleroma!1883

24 files changed:
CHANGELOG.md
docs/API/differences_in_mastoapi_responses.md
lib/pleroma/activity.ex
lib/pleroma/following_relationship.ex
lib/pleroma/notification.ex
lib/pleroma/user.ex
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/activity_pub/visibility.ex
lib/pleroma/web/common_api/utils.ex
lib/pleroma/web/mastodon_api/controllers/account_controller.ex
lib/pleroma/web/mastodon_api/views/account_view.ex
lib/pleroma/web/mastodon_api/views/notification_view.ex
lib/pleroma/web/push/impl.ex
lib/pleroma/workers/background_worker.ex
priv/repo/migrations/20191025081729_add_move_support_to_users.exs [new file with mode: 0644]
priv/static/schemas/litepub-0.1.jsonld
test/fixtures/tesla_mock/admin@mastdon.example.org.json
test/notification_test.exs
test/web/activity_pub/activity_pub_test.exs
test/web/activity_pub/transmogrifier_test.exs
test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
test/web/mastodon_api/views/account_view_test.exs
test/web/mastodon_api/views/notification_view_test.exs

index e44c892abd87d6c83fc06ccf2f2309b1ce564cba..a06ea211eb9dcf6b401c799c5357d3b8d04a1093 100644 (file)
@@ -66,6 +66,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
   - `DELETE /api/pleroma/admin/users` (`nickname` query param or `nickname` sent in JSON body) is deprecated in favor of: `DELETE /api/pleroma/admin/users` (`nicknames` query array param or `nicknames` sent in JSON body)
 - Admin API: Add `GET /api/pleroma/admin/relay` endpoint - lists all followed relays
 - Pleroma API: `POST /api/v1/pleroma/conversations/read` to mark all conversations as read
+- ActivityPub: Support `Move` activities
 - Mastodon API: Add `/api/v1/markers` for managing timeline read markers
 - Mastodon API: Add the `recipients` parameter to `GET /api/v1/conversations`
 - Configuration: `feed` option for user atom feed.
index 7fbe171306f54e41369bea8dc3b9184fcca2d903..006d17c1bc28abd63ae6331f0cf9e9ab99dbac9a 100644 (file)
@@ -57,6 +57,7 @@ Has these additional fields under the `pleroma` object:
 - `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`
 - `chat_token`: The token needed for Pleroma chat. Only returned in `verify_credentials`
 - `deactivated`: boolean, true when the user is deactivated
+- `allow_following_move`: boolean, true when the user allows automatically follow moved following accounts
 - `unread_conversation_count`: The count of unread conversations. Only returned to the account owner.
 
 ### Source
@@ -91,6 +92,12 @@ Has these additional fields under the `pleroma` object:
 
 - `is_seen`: true if the notification was read by the user
 
+### Move Notification
+
+The `type` value is `move`. Has an additional field:
+
+- `target`: new account
+
 ## GET `/api/v1/notifications`
 
 Accepts additional parameters:
@@ -136,6 +143,7 @@ Additional parameters can be added to the JSON body/Form data:
 - `default_scope` - the scope returned under `privacy` key in Source subentity
 - `pleroma_settings_store` - Opaque user settings to be saved on the backend.
 - `skip_thread_containment` - if true, skip filtering out broken threads
+- `allow_following_move` - if true, allows automatically follow moved following accounts
 - `pleroma_background_image` - sets the background image of the user.
 
 ### Pleroma Settings Store
index cd7a5aae9aba85848274103c9de1e95f757a78b5..f180c1e3311021a92ea4e2bd40dd53e877d2fa6c 100644 (file)
@@ -28,7 +28,8 @@ defmodule Pleroma.Activity do
     "Create" => "mention",
     "Follow" => "follow",
     "Announce" => "reblog",
-    "Like" => "favourite"
+    "Like" => "favourite",
+    "Move" => "move"
   }
 
   @mastodon_to_ap_notification_types for {k, v} <- @mastodon_notification_types,
index 3aff9fb76ba6dee1b1f47734f66028918371911f..a03c9bd304c595bb6e16832866001328c4951a2f 100644 (file)
@@ -107,4 +107,22 @@ defmodule Pleroma.FollowingRelationship do
       [user.follower_address | following]
     end
   end
+
+  def move_following(origin, target) do
+    __MODULE__
+    |> join(:inner, [r], f in assoc(r, :follower))
+    |> where(following_id: ^origin.id)
+    |> where([r, f], f.allow_following_move == true)
+    |> limit(50)
+    |> preload([:follower])
+    |> Repo.all()
+    |> Enum.map(fn following_relationship ->
+      Repo.delete(following_relationship)
+      Pleroma.Web.CommonAPI.follow(following_relationship.follower, target)
+    end)
+    |> case do
+      [] -> :ok
+      _ -> move_following(origin, target)
+    end
+  end
 end
index b7ecf51e4681fc9a25b8d1cd9d596c928f930bba..f37e7ec671ee88c39848cc92a7f715621766999e 100644 (file)
@@ -251,10 +251,13 @@ defmodule Pleroma.Notification do
     end
   end
 
-  def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
-      when type in ["Like", "Announce", "Follow"] do
-    users = get_notified_from_activity(activity)
-    notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
+  def create_notifications(%Activity{data: %{"type" => type}} = activity)
+      when type in ["Like", "Announce", "Follow", "Move"] do
+    notifications =
+      activity
+      |> get_notified_from_activity()
+      |> Enum.map(&create_notification(activity, &1))
+
     {:ok, notifications}
   end
 
@@ -276,19 +279,15 @@ defmodule Pleroma.Notification do
 
   def get_notified_from_activity(activity, local_only \\ true)
 
-  def get_notified_from_activity(
-        %Activity{data: %{"to" => _, "type" => type} = _data} = activity,
-        local_only
-      )
-      when type in ["Create", "Like", "Announce", "Follow"] do
-    recipients =
-      []
-      |> Utils.maybe_notify_to_recipients(activity)
-      |> Utils.maybe_notify_mentioned_recipients(activity)
-      |> Utils.maybe_notify_subscribers(activity)
-      |> Enum.uniq()
-
-    User.get_users_from_set(recipients, local_only)
+  def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only)
+      when type in ["Create", "Like", "Announce", "Follow", "Move"] do
+    []
+    |> Utils.maybe_notify_to_recipients(activity)
+    |> Utils.maybe_notify_mentioned_recipients(activity)
+    |> Utils.maybe_notify_subscribers(activity)
+    |> Utils.maybe_notify_followers(activity)
+    |> Enum.uniq()
+    |> User.get_users_from_set(local_only)
   end
 
   def get_notified_from_activity(_, _local_only), do: []
index 8851b0a7998626afcf19357441a8c743986fd338..120cb2641252589cfdc36169451833a67d668252 100644 (file)
@@ -103,7 +103,9 @@ defmodule Pleroma.User do
     field(:raw_fields, {:array, :map}, default: [])
     field(:discoverable, :boolean, default: false)
     field(:invisible, :boolean, default: false)
+    field(:allow_following_move, :boolean, default: true)
     field(:skip_thread_containment, :boolean, default: false)
+    field(:also_known_as, {:array, :string}, default: [])
 
     field(:notification_settings, :map,
       default: %{
@@ -251,7 +253,8 @@ defmodule Pleroma.User do
           :fields,
           :following_count,
           :discoverable,
-          :invisible
+          :invisible,
+          :also_known_as
         ]
       )
       |> validate_required([:name, :ap_id])
@@ -293,13 +296,15 @@ defmodule Pleroma.User do
         :hide_followers_count,
         :hide_follows_count,
         :hide_favorites,
+        :allow_following_move,
         :background,
         :show_role,
         :skip_thread_containment,
         :fields,
         :raw_fields,
         :pleroma_settings_store,
-        :discoverable
+        :discoverable,
+        :also_known_as
       ]
     )
     |> unique_constraint(:nickname)
@@ -337,9 +342,11 @@ defmodule Pleroma.User do
         :hide_follows,
         :fields,
         :hide_followers,
+        :allow_following_move,
         :discoverable,
         :hide_followers_count,
-        :hide_follows_count
+        :hide_follows_count,
+        :also_known_as
       ]
     )
     |> unique_constraint(:nickname)
index f32d0417596e1dac0c5a7ae5608fbf96a58d1b13..d6a425d8bd0f6628e4a1ec606402806c4fea89d3 100644 (file)
@@ -541,6 +541,30 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     end
   end
 
+  def move(%User{} = origin, %User{} = target, local \\ true) do
+    params = %{
+      "type" => "Move",
+      "actor" => origin.ap_id,
+      "object" => origin.ap_id,
+      "target" => target.ap_id
+    }
+
+    with true <- origin.ap_id in target.also_known_as,
+         {:ok, activity} <- insert(params, local) do
+      maybe_federate(activity)
+
+      BackgroundWorker.enqueue("move_following", %{
+        "origin_id" => origin.id,
+        "target_id" => target.id
+      })
+
+      {:ok, activity}
+    else
+      false -> {:error, "Target account must have the origin in `alsoKnownAs`"}
+      err -> err
+    end
+  end
+
   defp fetch_activities_for_context_query(context, opts) do
     public = [Pleroma.Constants.as_public()]
 
@@ -1171,7 +1195,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
       name: data["name"],
       follower_address: data["followers"],
       following_address: data["following"],
-      bio: data["summary"]
+      bio: data["summary"],
+      also_known_as: Map.get(data, "alsoKnownAs", [])
     }
 
     # nickname can be nil because of virtual actors
index 15612545bd376fd97daddb81d2dc11e489683332..ce95fb6babf84a0c7ac38359c7bae335f2dea5aa 100644 (file)
@@ -669,7 +669,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
       update_data =
         new_user_data
-        |> Map.take([:avatar, :banner, :bio, :name])
+        |> Map.take([:avatar, :banner, :bio, :name, :also_known_as])
         |> Map.put(:fields, fields)
         |> Map.put(:locked, locked)
         |> Map.put(:invisible, invisible)
@@ -857,6 +857,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     end
   end
 
+  def handle_incoming(
+        %{
+          "type" => "Move",
+          "actor" => origin_actor,
+          "object" => origin_actor,
+          "target" => target_actor
+        },
+        _options
+      ) do
+    with %User{} = origin_user <- User.get_cached_by_ap_id(origin_actor),
+         {:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_actor),
+         true <- origin_actor in target_user.also_known_as do
+      ActivityPub.move(origin_user, target_user, false)
+    else
+      _e -> :error
+    end
+  end
+
   def handle_incoming(_, _), do: :error
 
   @spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil
index cd409749348678e01a6ad3f14fd912ab0dc381ad..e172f6d3f2b6196338900b6448e80e17083b9b83 100644 (file)
@@ -14,6 +14,7 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
   @spec is_public?(Object.t() | Activity.t() | map()) :: boolean()
   def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
   def is_public?(%Object{data: data}), do: is_public?(data)
+  def is_public?(%Activity{data: %{"type" => "Move"}}), do: true
   def is_public?(%Activity{data: data}), do: is_public?(data)
   def is_public?(%{"directMessage" => true}), do: false
   def is_public?(data), do: Utils.label_in_message?(Pleroma.Constants.as_public(), data)
index 88a5f434a671277c0a949d22ad4173f0c07b5f37..cbb64f8d24f1c3cd2b2d4e4d8e96791504247a57 100644 (file)
@@ -451,6 +451,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do
     recipients ++ to
   end
 
+  def maybe_notify_to_recipients(recipients, _), do: recipients
+
   def maybe_notify_mentioned_recipients(
         recipients,
         %Activity{data: %{"to" => _to, "type" => type} = data} = activity
@@ -502,6 +504,17 @@ defmodule Pleroma.Web.CommonAPI.Utils do
 
   def maybe_notify_subscribers(recipients, _), do: recipients
 
+  def maybe_notify_followers(recipients, %Activity{data: %{"type" => "Move"}} = activity) do
+    with %User{} = user <- User.get_cached_by_ap_id(activity.actor) do
+      user
+      |> User.get_followers()
+      |> Enum.map(& &1.ap_id)
+      |> Enum.concat(recipients)
+    end
+  end
+
+  def maybe_notify_followers(recipients, _), do: recipients
+
   def maybe_extract_mentions(%{"tag" => tag}) do
     tag
     |> Enum.filter(fn x -> is_map(x) && x["type"] == "Mention" end)
index 5f2544640427afb24b91f8fe81e4e587789fd907..a69423f60eda71e54005b70c058136cc7ad0b925 100644 (file)
@@ -152,6 +152,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
         :hide_favorites,
         :show_role,
         :skip_thread_containment,
+        :allow_following_move,
         :discoverable
       ]
       |> Enum.reduce(%{}, fn key, acc ->
index 1068f8823f0ecd464963f84976220f91c5f9a9ff..ec720e472e20bd368c80cf79fa82922239bf5a50 100644 (file)
@@ -162,6 +162,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
     |> maybe_put_chat_token(user, opts[:for], opts)
     |> maybe_put_activation_status(user, opts[:for])
     |> maybe_put_follow_requests_count(user, opts[:for])
+    |> maybe_put_allow_following_move(user, opts[:for])
     |> maybe_put_unread_conversation_count(user, opts[:for])
   end
 
@@ -238,6 +239,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
 
   defp maybe_put_notification_settings(data, _, _), do: data
 
+  defp maybe_put_allow_following_move(data, %User{id: user_id} = user, %User{id: user_id}) do
+    Kernel.put_in(data, [:pleroma, :allow_following_move], user.allow_following_move)
+  end
+
+  defp maybe_put_allow_following_move(data, _, _), do: data
+
   defp maybe_put_activation_status(data, user, %User{is_admin: true}) do
     Kernel.put_in(data, [:pleroma, :deactivated], user.deactivated)
   end
index 5e3dbe728335d09dbd70b641bcb22c0e63349dc6..ddd7f531826d4bb18bc944770abd31b83be66b7a 100644 (file)
@@ -37,32 +37,24 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
       }
 
       case mastodon_type do
-        "mention" ->
-          response
-          |> Map.merge(%{
-            status: StatusView.render("show.json", %{activity: activity, for: user})
-          })
-
-        "favourite" ->
-          response
-          |> Map.merge(%{
-            status: StatusView.render("show.json", %{activity: parent_activity, for: user})
-          })
-
-        "reblog" ->
-          response
-          |> Map.merge(%{
-            status: StatusView.render("show.json", %{activity: parent_activity, for: user})
-          })
-
-        "follow" ->
-          response
-
-        _ ->
-          nil
+        "mention" -> put_status(response, activity, user)
+        "favourite" -> put_status(response, parent_activity, user)
+        "reblog" -> put_status(response, parent_activity, user)
+        "move" -> put_target(response, activity, user)
+        "follow" -> response
+        _ -> nil
       end
     else
       _ -> nil
     end
   end
+
+  defp put_status(response, activity, user) do
+    Map.put(response, :status, StatusView.render("show.json", %{activity: activity, for: user}))
+  end
+
+  defp put_target(response, activity, user) do
+    target = User.get_cached_by_ap_id(activity.data["target"])
+    Map.put(response, :target, AccountView.render("show.json", %{user: target, for: user}))
+  end
 end
index 3de7af7086ca8f1c7814529defa538d6fd1d0da5..a6a924d02f067191ddda7e157ffbb40209fd2f12 100644 (file)
@@ -16,7 +16,7 @@ defmodule Pleroma.Web.Push.Impl do
   require Logger
   import Ecto.Query
 
-  @types ["Create", "Follow", "Announce", "Like"]
+  @types ["Create", "Follow", "Announce", "Like", "Move"]
 
   @doc "Performs sending notifications for user subscriptions"
   @spec perform(Notification.t()) :: list(any) | :error
index 7ffc8eabec217ae19db9ec72c5465353526b4eeb..323a4da1ea2d73ae1e2e56af42cd92dcbacb0a76 100644 (file)
@@ -71,4 +71,11 @@ defmodule Pleroma.Workers.BackgroundWorker do
     activity = Activity.get_by_id(activity_id)
     Pleroma.Web.RichMedia.Helpers.perform(:fetch, activity)
   end
+
+  def perform(%{"op" => "move_following", "origin_id" => origin_id, "target_id" => target_id}, _) do
+    origin = User.get_cached_by_id(origin_id)
+    target = User.get_cached_by_id(target_id)
+
+    Pleroma.FollowingRelationship.move_following(origin, target)
+  end
 end
diff --git a/priv/repo/migrations/20191025081729_add_move_support_to_users.exs b/priv/repo/migrations/20191025081729_add_move_support_to_users.exs
new file mode 100644 (file)
index 0000000..580b9eb
--- /dev/null
@@ -0,0 +1,10 @@
+defmodule Pleroma.Repo.Migrations.AddMoveSupportToUsers do
+  use Ecto.Migration
+
+  def change do
+    alter table(:users) do
+      add(:also_known_as, {:array, :string}, default: [], null: false)
+      add(:allow_following_move, :boolean, default: true, null: false)
+    end
+  end
+end
index 8bae42f6da9e4e80a877a58b864fc2131e663d7e..e7ebf72be15630be73d62c53d244374ee7101946 100644 (file)
                 "@id": "litepub:oauthRegistrationEndpoint",
                 "@type": "@id"
             },
-            "EmojiReaction": "litepub:EmojiReaction"
+            "EmojiReaction": "litepub:EmojiReaction",
+            "alsoKnownAs": {
+                "@id": "as:alsoKnownAs",
+                "@type": "@id"
+            }
         }
     ]
 }
index 8159dc20ad0e3c0ba52e7e16ba2fcbc0be4ee1f8..9fdd6557ca26095df858b17b4dd7e6adf58d8aaf 100644 (file)
@@ -9,7 +9,11 @@
     "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
     "conversation": "ostatus:conversation",
     "toot": "http://joinmastodon.org/ns#",
-    "Emoji": "toot:Emoji"
+    "Emoji": "toot:Emoji",
+    "alsoKnownAs": {
+      "@id": "as:alsoKnownAs",
+      "@type": "@id"
+    }
   }],
   "id": "http://mastodon.example.org/users/admin",
   "type": "Person",
@@ -50,5 +54,6 @@
     "type": "Image",
     "mediaType": "image/png",
     "url": "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
-  }
+  },
+  "alsoKnownAs": ["http://example.org/users/foo"]
 }
index f8d42922322353a2d509dc8fc2c4c33313db186a..dcbffeafe39b16c1afdf7505fc4744cf2119ce25 100644 (file)
@@ -630,6 +630,35 @@ defmodule Pleroma.NotificationTest do
 
       assert Enum.empty?(Notification.for_user(local_user))
     end
+
+    test "move activity generates a notification" do
+      %{ap_id: old_ap_id} = old_user = insert(:user)
+      %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
+      follower = insert(:user)
+      other_follower = insert(:user, %{allow_following_move: false})
+
+      User.follow(follower, old_user)
+      User.follow(other_follower, old_user)
+
+      Pleroma.Web.ActivityPub.ActivityPub.move(old_user, new_user)
+      ObanHelpers.perform_all()
+
+      assert [
+               %{
+                 activity: %{
+                   data: %{"type" => "Move", "actor" => ^old_ap_id, "target" => ^new_ap_id}
+                 }
+               }
+             ] = Notification.for_user(follower)
+
+      assert [
+               %{
+                 activity: %{
+                   data: %{"type" => "Move", "actor" => ^old_ap_id, "target" => ^new_ap_id}
+                 }
+               }
+             ] = Notification.for_user(other_follower)
+    end
   end
 
   describe "for_user" do
index 30f5dde0cb344830cd355b60edbd6d37d0165e50..2677b9e36bbd48b1a700a2dc9f5811f997dc237a 100644 (file)
@@ -4,8 +4,11 @@
 
 defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
   use Pleroma.DataCase
+  use Oban.Testing, repo: Pleroma.Repo
+
   alias Pleroma.Activity
   alias Pleroma.Builders.ActivityBuilder
+  alias Pleroma.Notification
   alias Pleroma.Object
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
@@ -1570,4 +1573,64 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       assert follow_info.hide_follows == true
     end
   end
+
+  describe "Move activity" do
+    test "create" do
+      %{ap_id: old_ap_id} = old_user = insert(:user)
+      %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
+      follower = insert(:user)
+      follower_move_opted_out = insert(:user, allow_following_move: false)
+
+      User.follow(follower, old_user)
+      User.follow(follower_move_opted_out, old_user)
+
+      assert User.following?(follower, old_user)
+      assert User.following?(follower_move_opted_out, old_user)
+
+      assert {:ok, activity} = ActivityPub.move(old_user, new_user)
+
+      assert %Activity{
+               actor: ^old_ap_id,
+               data: %{
+                 "actor" => ^old_ap_id,
+                 "object" => ^old_ap_id,
+                 "target" => ^new_ap_id,
+                 "type" => "Move"
+               },
+               local: true
+             } = activity
+
+      params = %{
+        "op" => "move_following",
+        "origin_id" => old_user.id,
+        "target_id" => new_user.id
+      }
+
+      assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
+
+      Pleroma.Workers.BackgroundWorker.perform(params, nil)
+
+      refute User.following?(follower, old_user)
+      assert User.following?(follower, new_user)
+
+      assert User.following?(follower_move_opted_out, old_user)
+      refute User.following?(follower_move_opted_out, new_user)
+
+      activity = %Activity{activity | object: nil}
+
+      assert [%Notification{activity: ^activity}] =
+               Notification.for_user_since(follower, ~N[2019-04-13 11:22:33])
+
+      assert [%Notification{activity: ^activity}] =
+               Notification.for_user_since(follower_move_opted_out, ~N[2019-04-13 11:22:33])
+    end
+
+    test "old user must be in the new user's `also_known_as` list" do
+      old_user = insert(:user)
+      new_user = insert(:user)
+
+      assert {:error, "Target account must have the origin in `alsoKnownAs`"} =
+               ActivityPub.move(old_user, new_user)
+    end
+  end
 end
index b31c411dc3ea32cb5948bdbd1b2df5183287343a..5da358c43636e10fa0556dfbb3d4b72393c43bfe 100644 (file)
@@ -683,6 +683,37 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       assert user.bio == "<p>Some bio</p>"
     end
 
+    test "it works with alsoKnownAs" do
+      {:ok, %Activity{data: %{"actor" => actor}}} =
+        "test/fixtures/mastodon-post-activity.json"
+        |> File.read!()
+        |> Poison.decode!()
+        |> Transmogrifier.handle_incoming()
+
+      assert User.get_cached_by_ap_id(actor).also_known_as == ["http://example.org/users/foo"]
+
+      {:ok, _activity} =
+        "test/fixtures/mastodon-update.json"
+        |> File.read!()
+        |> Poison.decode!()
+        |> Map.put("actor", actor)
+        |> Map.update!("object", fn object ->
+          object
+          |> Map.put("actor", actor)
+          |> Map.put("id", actor)
+          |> Map.put("alsoKnownAs", [
+            "http://mastodon.example.org/users/foo",
+            "http://example.org/users/bar"
+          ])
+        end)
+        |> Transmogrifier.handle_incoming()
+
+      assert User.get_cached_by_ap_id(actor).also_known_as == [
+               "http://mastodon.example.org/users/foo",
+               "http://example.org/users/bar"
+             ]
+    end
+
     test "it works with custom profile fields" do
       {:ok, activity} =
         "test/fixtures/mastodon-post-activity.json"
@@ -1272,6 +1303,30 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
       assert [user.follower_address] == activity.data["to"]
     end
+
+    test "it accepts Move activities" do
+      old_user = insert(:user)
+      new_user = insert(:user)
+
+      message = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "type" => "Move",
+        "actor" => old_user.ap_id,
+        "object" => old_user.ap_id,
+        "target" => new_user.ap_id
+      }
+
+      assert :error = Transmogrifier.handle_incoming(message)
+
+      {:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]})
+
+      assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message)
+      assert activity.actor == old_user.ap_id
+      assert activity.data["actor"] == old_user.ap_id
+      assert activity.data["object"] == old_user.ap_id
+      assert activity.data["target"] == new_user.ap_id
+      assert activity.data["type"] == "Move"
+    end
   end
 
   describe "prepare outgoing" do
index 519b56d6cb64255f00ba55e34baffe74ad783012..77cfce4fa8e10fbcc0f6730f069434e75c7cf3be 100644 (file)
@@ -103,6 +103,21 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       assert user["locked"] == true
     end
 
+    test "updates the user's allow_following_move", %{conn: conn} do
+      user = insert(:user)
+
+      assert user.allow_following_move == true
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> patch("/api/v1/accounts/update_credentials", %{allow_following_move: "false"})
+
+      assert refresh_record(user).allow_following_move == false
+      assert user = json_response(conn, 200)
+      assert user["pleroma"]["allow_following_move"] == false
+    end
+
     test "updates the user's default scope", %{conn: conn} do
       user = insert(:user)
 
index d147079ab603f3e2b2592c25dfaa2eda2feb045b..35aefb7dc08f4efbd0cadb0f75cc767c60e7166a 100644 (file)
@@ -102,7 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
     privacy = user.default_scope
 
     assert %{
-             pleroma: %{notification_settings: ^notification_settings},
+             pleroma: %{notification_settings: ^notification_settings, allow_following_move: true},
              source: %{privacy: ^privacy}
            } = AccountView.render("show.json", %{user: user, for: user})
   end
index c9043a69ada032c61f404dcf7c96800c9c8ecdd5..80b6d414cf178a5494b0fa209a6964ac0e502e3a 100644 (file)
@@ -107,4 +107,28 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
     assert [] ==
              NotificationView.render("index.json", %{notifications: [notification], for: followed})
   end
+
+  test "Move notification" do
+    %{ap_id: old_ap_id} = old_user = insert(:user)
+    %{ap_id: _new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
+    follower = insert(:user)
+
+    User.follow(follower, old_user)
+    Pleroma.Web.ActivityPub.ActivityPub.move(old_user, new_user)
+    Pleroma.Tests.ObanHelpers.perform_all()
+
+    [notification] = Notification.for_user(follower)
+
+    expected = %{
+      id: to_string(notification.id),
+      pleroma: %{is_seen: false},
+      type: "move",
+      account: AccountView.render("show.json", %{user: old_user, for: follower}),
+      target: AccountView.render("show.json", %{user: new_user, for: follower}),
+      created_at: Utils.to_masto_date(notification.inserted_at)
+    }
+
+    assert [expected] ==
+             NotificationView.render("index.json", %{notifications: [notification], for: follower})
+  end
 end