Merge branch 'stream-follow-updates' into 'develop'

Stream follow updates

Closes #2299

See merge request pleroma/pleroma!3183
This commit is contained in:
lain 2020-12-09 15:52:31 +00:00
commit 1436a2fa2d
32 changed files with 270 additions and 155 deletions

View file

@ -33,7 +33,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Pleroma API: (`GET /api/v1/pleroma/federation_status`) Add a way to get a list of unreachable instances. - Pleroma API: (`GET /api/v1/pleroma/federation_status`) Add a way to get a list of unreachable instances.
- Mastodon API: User and conversation mutes can now auto-expire if `expires_in` parameter was given while adding the mute. - Mastodon API: User and conversation mutes can now auto-expire if `expires_in` parameter was given while adding the mute.
- Admin API: An endpoint to manage frontends - Admin API: An endpoint to manage frontends
- Streaming API: Add follow relationships updates
</details> </details>
### Fixed ### Fixed

View file

@ -109,8 +109,8 @@ def make_friends(main_user, max) when is_integer(max) do
end end
def make_friends(%User{} = main_user, %User{} = user) do def make_friends(%User{} = main_user, %User{} = user) do
{:ok, _} = User.follow(main_user, user) {:ok, _, _} = User.follow(main_user, user)
{:ok, _} = User.follow(user, main_user) {:ok, _, _} = User.follow(user, main_user)
end end
@spec get_users(User.t(), keyword()) :: [User.t()] @spec get_users(User.t(), keyword()) :: [User.t()]

View file

@ -50,7 +50,7 @@ def run(_args) do
) )
users users
|> Enum.each(fn {:ok, follower} -> Pleroma.User.follow(follower, user) end) |> Enum.each(fn {:ok, follower, user} -> Pleroma.User.follow(follower, user) end)
Benchee.run( Benchee.run(
%{ %{

View file

@ -4,7 +4,7 @@ A Pleroma instance can be identified by "<Mastodon version> (compatible; Pleroma
## Flake IDs ## Flake IDs
Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are lexically sortable strings Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However, just like Mastodon's ids, they are lexically sortable strings
## Timelines ## Timelines
@ -26,8 +26,8 @@ Has these additional fields under the `pleroma` object:
- `conversation_id`: the ID of the AP context the status is associated with (if any) - `conversation_id`: the ID of the AP context the status is associated with (if any)
- `direct_conversation_id`: the ID of the Mastodon direct message conversation the status is associated with (if any) - `direct_conversation_id`: the ID of the Mastodon direct message conversation the status is associated with (if any)
- `in_reply_to_account_acct`: the `acct` property of User entity for replied user (if any) - `in_reply_to_account_acct`: the `acct` property of User entity for replied user (if any)
- `content`: a map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` - `content`: a map consisting of alternate representations of the `content` property with the key being its mimetype. Currently, the only alternate representation supported is `text/plain`
- `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` - `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being its mimetype. Currently, the only alternate representation supported is `text/plain`
- `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire - `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire
- `thread_muted`: true if the thread the post belongs to is muted - `thread_muted`: true if the thread the post belongs to is muted
- `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint. - `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint.
@ -170,9 +170,9 @@ Returns on success: 200 OK `{}`
Additional parameters can be added to the JSON body/Form data: Additional parameters can be added to the JSON body/Form data:
- `preview`: boolean, if set to `true` the post won't be actually posted, but the status entitiy would still be rendered back. This could be useful for previewing rich text/custom emoji, for example. - `preview`: boolean, if set to `true` the post won't be actually posted, but the status entity would still be rendered back. This could be useful for previewing rich text/custom emoji, for example.
- `content_type`: string, contain the MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint. - `content_type`: string, contain the MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint.
- `to`: A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for for post visibility are not affected by this and will still apply. - `to`: A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for post visibility are not affected by this and will still apply.
- `visibility`: string, besides standard MastoAPI values (`direct`, `private`, `unlisted`, `local` or `public`) it can be used to address a List by setting it to `list:LIST_ID`. - `visibility`: string, besides standard MastoAPI values (`direct`, `private`, `unlisted`, `local` or `public`) it can be used to address a List by setting it to `list:LIST_ID`.
- `expires_in`: The number of seconds the posted activity should expire in. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated. This needs to be longer than an hour. - `expires_in`: The number of seconds the posted activity should expire in. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated. This needs to be longer than an hour.
- `in_reply_to_conversation_id`: Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`. - `in_reply_to_conversation_id`: Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`.
@ -279,10 +279,27 @@ Has these additional fields under the `pleroma` object:
## Streaming ## Streaming
### Chats
There is an additional `user:pleroma_chat` stream. Incoming chat messages will make the current chat be sent to this `user` stream. The `event` of an incoming chat message is `pleroma:chat_update`. The payload is the updated chat with the incoming chat message in the `last_message` field. There is an additional `user:pleroma_chat` stream. Incoming chat messages will make the current chat be sent to this `user` stream. The `event` of an incoming chat message is `pleroma:chat_update`. The payload is the updated chat with the incoming chat message in the `last_message` field.
### Remote timelines
For viewing remote server timelines, there are `public:remote` and `public:remote:media` streams. Each of these accept a parameter like `?instance=lain.com`. For viewing remote server timelines, there are `public:remote` and `public:remote:media` streams. Each of these accept a parameter like `?instance=lain.com`.
### Follow relationships updates
Pleroma streams follow relationships updates as `pleroma:follow_relationships_update` events to the `user` stream.
The message payload consist of:
- `state`: a relationship state, one of `follow_pending`, `follow_accept` or `follow_reject`.
- `follower` and `following` maps with following fields:
- `id`: user ID
- `follower_count`: follower count
- `following_count`: following count
## User muting and thread muting ## User muting and thread muting
Both user muting and thread muting can be done for only a certain time by adding an `expires_in` parameter to the API calls and giving the expiration time in seconds. Both user muting and thread muting can be done for only a certain time by adding an `expires_in` parameter to the API calls and giving the expiration time in seconds.

View file

@ -62,23 +62,47 @@ def update(%User{} = follower, %User{} = following, state) do
follow(follower, following, state) follow(follower, following, state)
following_relationship -> following_relationship ->
with {:ok, _following_relationship} <-
following_relationship following_relationship
|> cast(%{state: state}, [:state]) |> cast(%{state: state}, [:state])
|> validate_required([:state]) |> validate_required([:state])
|> Repo.update() |> Repo.update() do
after_update(state, follower, following)
end
end end
end end
def follow(%User{} = follower, %User{} = following, state \\ :follow_accept) do def follow(%User{} = follower, %User{} = following, state \\ :follow_accept) do
with {:ok, _following_relationship} <-
%__MODULE__{} %__MODULE__{}
|> changeset(%{follower: follower, following: following, state: state}) |> changeset(%{follower: follower, following: following, state: state})
|> Repo.insert(on_conflict: :nothing) |> Repo.insert(on_conflict: :nothing) do
after_update(state, follower, following)
end
end end
def unfollow(%User{} = follower, %User{} = following) do def unfollow(%User{} = follower, %User{} = following) do
case get(follower, following) do case get(follower, following) do
%__MODULE__{} = following_relationship -> Repo.delete(following_relationship) %__MODULE__{} = following_relationship ->
_ -> {:ok, nil} with {:ok, _following_relationship} <- Repo.delete(following_relationship) do
after_update(:unfollow, follower, following)
end
_ ->
{:ok, nil}
end
end
defp after_update(state, %User{} = follower, %User{} = following) do
with {:ok, following} <- User.update_follower_count(following),
{:ok, follower} <- User.update_following_count(follower) do
Pleroma.Web.Streamer.stream("follow_relationship", %{
state: state,
following: following,
follower: follower
})
{:ok, follower, following}
end end
end end

View file

@ -894,7 +894,7 @@ def maybe_direct_follow(%User{} = follower, %User{} = followed) do
if not ap_enabled?(followed) do if not ap_enabled?(followed) do
follow(follower, followed) follow(follower, followed)
else else
{:ok, follower} {:ok, follower, followed}
end end
end end
@ -920,11 +920,6 @@ def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
true -> true ->
FollowingRelationship.follow(follower, followed, state) FollowingRelationship.follow(follower, followed, state)
{:ok, _} = update_follower_count(followed)
follower
|> update_following_count()
end end
end end
@ -948,11 +943,6 @@ defp do_unfollow(%User{} = follower, %User{} = followed) do
case get_follow_state(follower, followed) do case get_follow_state(follower, followed) do
state when state in [:follow_pending, :follow_accept] -> state when state in [:follow_pending, :follow_accept] ->
FollowingRelationship.unfollow(follower, followed) FollowingRelationship.unfollow(follower, followed)
{:ok, followed} = update_follower_count(followed)
{:ok, follower} = update_following_count(follower)
{:ok, follower, followed}
nil -> nil ->
{:error, "Not subscribed!"} {:error, "Not subscribed!"}

View file

@ -45,7 +45,7 @@ def perform(:follow_import, %User{} = follower, [_ | _] = identifiers) do
identifiers, identifiers,
fn identifier -> fn identifier ->
with {:ok, %User{} = followed} <- User.get_or_fetch(identifier), with {:ok, %User{} = followed} <- User.get_or_fetch(identifier),
{:ok, follower} <- User.maybe_direct_follow(follower, followed), {:ok, follower, followed} <- User.maybe_direct_follow(follower, followed),
{:ok, _, _, _} <- CommonAPI.follow(follower, followed) do {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do
followed followed
else else

View file

@ -47,10 +47,9 @@ def handle(
%User{} = followed <- User.get_cached_by_ap_id(actor), %User{} = followed <- User.get_cached_by_ap_id(actor),
%User{} = follower <- User.get_cached_by_ap_id(follower_id), %User{} = follower <- User.get_cached_by_ap_id(follower_id),
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"), {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do {:ok, _follower, followed} <-
FollowingRelationship.update(follower, followed, :follow_accept) do
Notification.update_notification_type(followed, follow_activity) Notification.update_notification_type(followed, follow_activity)
User.update_follower_count(followed)
User.update_following_count(follower)
end end
{:ok, object, meta} {:ok, object, meta}
@ -99,7 +98,7 @@ def handle(
) do ) do
with %User{} = follower <- User.get_cached_by_ap_id(following_user), with %User{} = follower <- User.get_cached_by_ap_id(following_user),
%User{} = followed <- User.get_cached_by_ap_id(followed_user), %User{} = followed <- User.get_cached_by_ap_id(followed_user),
{_, {:ok, _}, _, _} <- {_, {:ok, _, _}, _, _} <-
{:following, User.follow(follower, followed, :follow_pending), follower, followed} do {:following, User.follow(follower, followed, :follow_pending), follower, followed} do
if followed.local && !followed.is_locked do if followed.local && !followed.is_locked do
{:ok, accept_data, _} = Builder.accept(followed, object) {:ok, accept_data, _} = Builder.accept(followed, object)

View file

@ -36,9 +36,8 @@ def registry, do: @registry
) :: ) ::
{:ok, topic :: String.t()} | {:error, :bad_topic} | {:error, :unauthorized} {:ok, topic :: String.t()} | {:error, :bad_topic} | {:error, :unauthorized}
def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do
case get_topic(stream, user, oauth_token, params) do with {:ok, topic} <- get_topic(stream, user, oauth_token, params) do
{:ok, topic} -> add_socket(topic, user) add_socket(topic, user)
error -> error
end end
end end
@ -70,10 +69,10 @@ def get_topic("public:remote:media", _user, _oauth_token, %{"instance" => instan
def get_topic( def get_topic(
stream, stream,
%User{id: user_id} = user, %User{id: user_id} = user,
%Token{user_id: token_user_id} = oauth_token, %Token{user_id: user_id} = oauth_token,
_params _params
) )
when stream in @user_streams and user_id == token_user_id do when stream in @user_streams do
# Note: "read" works for all user streams (not mentioning it since it's an ancestor scope) # Note: "read" works for all user streams (not mentioning it since it's an ancestor scope)
required_scopes = required_scopes =
if stream == "user:notification" do if stream == "user:notification" do
@ -97,10 +96,9 @@ def get_topic(stream, _user, _oauth_token, _params) when stream in @user_streams
def get_topic( def get_topic(
"list", "list",
%User{id: user_id} = user, %User{id: user_id} = user,
%Token{user_id: token_user_id} = oauth_token, %Token{user_id: user_id} = oauth_token,
%{"list" => id} %{"list" => id}
) ) do
when user_id == token_user_id do
cond do cond do
OAuthScopesPlug.filter_descendants(["read", "read:lists"], oauth_token.scopes) == [] -> OAuthScopesPlug.filter_descendants(["read", "read:lists"], oauth_token.scopes) == [] ->
{:error, :unauthorized} {:error, :unauthorized}
@ -137,16 +135,10 @@ def remove_socket(topic) do
def stream(topics, items) do def stream(topics, items) do
if should_env_send?() do if should_env_send?() do
List.wrap(topics) for topic <- List.wrap(topics), item <- List.wrap(items) do
|> Enum.each(fn topic ->
List.wrap(items)
|> Enum.each(fn item ->
spawn(fn -> do_stream(topic, item) end) spawn(fn -> do_stream(topic, item) end)
end)
end)
end end
end
:ok
end end
def filtered_by_user?(user, item, streamed_type \\ :activity) def filtered_by_user?(user, item, streamed_type \\ :activity)
@ -160,8 +152,7 @@ def filtered_by_user?(%User{} = user, %Activity{} = item, streamed_type) do
domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks) domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
with parent <- Object.normalize(item) || item, with parent <- Object.normalize(item) || item,
true <- true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)),
Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)),
true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids, true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids,
true <- true <-
!(streamed_type == :activity && item.data["type"] == "Announce" && !(streamed_type == :activity && item.data["type"] == "Announce" &&
@ -195,6 +186,19 @@ defp do_stream("direct", item) do
end) end)
end end
defp do_stream("follow_relationship", item) do
text = StreamerView.render("follow_relationships_update.json", item)
user_topic = "user:#{item.follower.id}"
Logger.debug("Trying to push follow relationship update to #{user_topic}\n\n")
Registry.dispatch(@registry, user_topic, fn list ->
Enum.each(list, fn {pid, _auth} ->
send(pid, {:text, text})
end)
end)
end
defp do_stream("participation", participation) do defp do_stream("participation", participation) do
user_topic = "direct:#{participation.user_id}" user_topic = "direct:#{participation.user_id}"
Logger.debug("Trying to push a conversation participation to #{user_topic}\n\n") Logger.debug("Trying to push a conversation participation to #{user_topic}\n\n")

View file

@ -74,6 +74,28 @@ def render("chat_update.json", %{chat_message_reference: cm_ref}) do
|> Jason.encode!() |> Jason.encode!()
end end
def render("follow_relationships_update.json", item) do
%{
event: "pleroma:follow_relationships_update",
payload:
%{
state: item.state,
follower: %{
id: item.follower.id,
follower_count: item.follower.follower_count,
following_count: item.follower.following_count
},
following: %{
id: item.following.id,
follower_count: item.following.follower_count,
following_count: item.following.following_count
}
}
|> Jason.encode!()
}
|> Jason.encode!()
end
def render("conversation.json", %Participation{} = participation) do def render("conversation.json", %Participation{} = participation) do
%{ %{
event: "conversation", event: "conversation",

View file

@ -73,7 +73,7 @@ test "it prunes old objects from the database" do
describe "running update_users_following_followers_counts" do describe "running update_users_following_followers_counts" do
test "following and followers count are updated" do test "following and followers count are updated" do
[user, user2] = insert_pair(:user) [user, user2] = insert_pair(:user)
{:ok, %User{} = user} = User.follow(user, user2) {:ok, %User{} = user, _user2} = User.follow(user, user2)
following = User.following(user) following = User.following(user)

View file

@ -503,7 +503,7 @@ test "it returns users matching" do
moot = insert(:user, nickname: "moot") moot = insert(:user, nickname: "moot")
kawen = insert(:user, nickname: "kawen", name: "fediverse expert moon") kawen = insert(:user, nickname: "kawen", name: "fediverse expert moon")
{:ok, user} = User.follow(user, moon) {:ok, user, moon} = User.follow(user, moon)
assert [moon.id, kawen.id] == User.Search.search("moon") |> Enum.map(& &1.id) assert [moon.id, kawen.id] == User.Search.search("moon") |> Enum.map(& &1.id)

View file

@ -19,7 +19,7 @@ test "getting the home timeline" do
user = insert(:user) user = insert(:user)
followed = insert(:user) followed = insert(:user)
{:ok, user} = User.follow(user, followed) {:ok, user, followed} = User.follow(user, followed)
{:ok, _first} = CommonAPI.post(user, %{status: "hey"}) {:ok, _first} = CommonAPI.post(user, %{status: "hey"})
{:ok, _second} = CommonAPI.post(followed, %{status: "hello"}) {:ok, _second} = CommonAPI.post(followed, %{status: "hello"})

View file

@ -779,7 +779,7 @@ test "it returns following domain-blocking recipient in enabled recipients list"
other_user = insert(:user) other_user = insert(:user)
{:ok, other_user} = User.block_domain(other_user, blocked_domain) {:ok, other_user} = User.block_domain(other_user, blocked_domain)
{:ok, other_user} = User.follow(other_user, user) {:ok, other_user, user} = User.follow(other_user, user)
{:ok, activity} = CommonAPI.post(user, %{status: "hey @#{other_user.nickname}!"}) {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{other_user.nickname}!"})
@ -1070,7 +1070,7 @@ test "it returns notifications for domain-blocked but followed user" do
blocked = insert(:user, ap_id: "http://some-domain.com") blocked = insert(:user, ap_id: "http://some-domain.com")
{:ok, user} = User.block_domain(user, "some-domain.com") {:ok, user} = User.block_domain(user, "some-domain.com")
{:ok, _} = User.follow(user, blocked) {:ok, _, _} = User.follow(user, blocked)
{:ok, _activity} = CommonAPI.post(blocked, %{status: "hey @#{user.nickname}"}) {:ok, _activity} = CommonAPI.post(blocked, %{status: "hey @#{user.nickname}"})

View file

@ -30,7 +30,7 @@ test "it imports user followings from list" do
assert {:ok, result} = ObanHelpers.perform(job) assert {:ok, result} = ObanHelpers.perform(job)
assert is_list(result) assert is_list(result)
assert result == [user2, user3] assert result == [refresh_record(user2), refresh_record(user3)]
assert User.following?(user1, user2) assert User.following?(user1, user2)
assert User.following?(user1, user3) assert User.following?(user1, user3)
end end

View file

@ -151,8 +151,8 @@ test "finds users, boosting ranks of friends and followers" do
follower = insert(:user, %{name: "Doe"}) follower = insert(:user, %{name: "Doe"})
friend = insert(:user, %{name: "Doe"}) friend = insert(:user, %{name: "Doe"})
{:ok, follower} = User.follow(follower, u1) {:ok, follower, u1} = User.follow(follower, u1)
{:ok, u1} = User.follow(u1, friend) {:ok, u1, friend} = User.follow(u1, friend)
assert [friend.id, follower.id, u2.id] -- assert [friend.id, follower.id, u2.id] --
Enum.map(User.search("doe", resolve: false, for_user: u1), & &1.id) == [] Enum.map(User.search("doe", resolve: false, for_user: u1), & &1.id) == []
@ -165,9 +165,9 @@ test "finds followings of user by partial name" do
following_jimi = insert(:user, %{name: "Lizz Wright"}) following_jimi = insert(:user, %{name: "Lizz Wright"})
follower_lizz = insert(:user, %{name: "Jimi"}) follower_lizz = insert(:user, %{name: "Jimi"})
{:ok, lizz} = User.follow(lizz, following_lizz) {:ok, lizz, following_lizz} = User.follow(lizz, following_lizz)
{:ok, _jimi} = User.follow(jimi, following_jimi) {:ok, _jimi, _following_jimi} = User.follow(jimi, following_jimi)
{:ok, _follower_lizz} = User.follow(follower_lizz, lizz) {:ok, _follower_lizz, _lizz} = User.follow(follower_lizz, lizz)
assert Enum.map(User.search("jimi", following: true, for_user: lizz), & &1.id) == [ assert Enum.map(User.search("jimi", following: true, for_user: lizz), & &1.id) == [
following_lizz.id following_lizz.id

View file

@ -233,7 +233,7 @@ test "follow_all follows mutliple users" do
{:ok, _user_relationship} = User.block(user, blocked) {:ok, _user_relationship} = User.block(user, blocked)
{:ok, _user_relationship} = User.block(reverse_blocked, user) {:ok, _user_relationship} = User.block(reverse_blocked, user)
{:ok, user} = User.follow(user, followed_zero) {:ok, user, followed_zero} = User.follow(user, followed_zero)
{:ok, user} = User.follow_all(user, [followed_one, followed_two, blocked, reverse_blocked]) {:ok, user} = User.follow_all(user, [followed_one, followed_two, blocked, reverse_blocked])
@ -262,7 +262,7 @@ test "follow takes a user and another user" do
user = insert(:user) user = insert(:user)
followed = insert(:user) followed = insert(:user)
{:ok, user} = User.follow(user, followed) {:ok, user, followed} = User.follow(user, followed)
user = User.get_cached_by_id(user.id) user = User.get_cached_by_id(user.id)
followed = User.get_cached_by_ap_id(followed.ap_id) followed = User.get_cached_by_ap_id(followed.ap_id)
@ -302,7 +302,7 @@ test "local users do not automatically follow local locked accounts" do
follower = insert(:user, is_locked: true) follower = insert(:user, is_locked: true)
followed = insert(:user, is_locked: true) followed = insert(:user, is_locked: true)
{:ok, follower} = User.maybe_direct_follow(follower, followed) {:ok, follower, followed} = User.maybe_direct_follow(follower, followed)
refute User.following?(follower, followed) refute User.following?(follower, followed)
end end
@ -330,7 +330,7 @@ test "unfollow with syncronizes external user" do
following_address: "http://localhost:4001/users/fuser2/following" following_address: "http://localhost:4001/users/fuser2/following"
}) })
{:ok, user} = User.follow(user, followed, :follow_accept) {:ok, user, followed} = User.follow(user, followed, :follow_accept)
{:ok, user, _activity} = User.unfollow(user, followed) {:ok, user, _activity} = User.unfollow(user, followed)
@ -343,7 +343,7 @@ test "unfollow takes a user and another user" do
followed = insert(:user) followed = insert(:user)
user = insert(:user) user = insert(:user)
{:ok, user} = User.follow(user, followed, :follow_accept) {:ok, user, followed} = User.follow(user, followed, :follow_accept)
assert User.following(user) == [user.follower_address, followed.follower_address] assert User.following(user) == [user.follower_address, followed.follower_address]
@ -911,8 +911,8 @@ test "gets all followers for a given user" do
follower_two = insert(:user) follower_two = insert(:user)
not_follower = insert(:user) not_follower = insert(:user)
{:ok, follower_one} = User.follow(follower_one, user) {:ok, follower_one, user} = User.follow(follower_one, user)
{:ok, follower_two} = User.follow(follower_two, user) {:ok, follower_two, user} = User.follow(follower_two, user)
res = User.get_followers(user) res = User.get_followers(user)
@ -927,8 +927,8 @@ test "gets all friends (followed users) for a given user" do
followed_two = insert(:user) followed_two = insert(:user)
not_followed = insert(:user) not_followed = insert(:user)
{:ok, user} = User.follow(user, followed_one) {:ok, user, followed_one} = User.follow(user, followed_one)
{:ok, user} = User.follow(user, followed_two) {:ok, user, followed_two} = User.follow(user, followed_two)
res = User.get_friends(user) res = User.get_friends(user)
@ -1098,8 +1098,8 @@ test "blocks tear down cyclical follow relationships" do
blocker = insert(:user) blocker = insert(:user)
blocked = insert(:user) blocked = insert(:user)
{:ok, blocker} = User.follow(blocker, blocked) {:ok, blocker, blocked} = User.follow(blocker, blocked)
{:ok, blocked} = User.follow(blocked, blocker) {:ok, blocked, blocker} = User.follow(blocked, blocker)
assert User.following?(blocker, blocked) assert User.following?(blocker, blocked)
assert User.following?(blocked, blocker) assert User.following?(blocked, blocker)
@ -1117,7 +1117,7 @@ test "blocks tear down blocker->blocked follow relationships" do
blocker = insert(:user) blocker = insert(:user)
blocked = insert(:user) blocked = insert(:user)
{:ok, blocker} = User.follow(blocker, blocked) {:ok, blocker, blocked} = User.follow(blocker, blocked)
assert User.following?(blocker, blocked) assert User.following?(blocker, blocked)
refute User.following?(blocked, blocker) refute User.following?(blocked, blocker)
@ -1135,7 +1135,7 @@ test "blocks tear down blocked->blocker follow relationships" do
blocker = insert(:user) blocker = insert(:user)
blocked = insert(:user) blocked = insert(:user)
{:ok, blocked} = User.follow(blocked, blocker) {:ok, blocked, blocker} = User.follow(blocked, blocker)
refute User.following?(blocker, blocked) refute User.following?(blocker, blocked)
assert User.following?(blocked, blocker) assert User.following?(blocked, blocker)
@ -1233,7 +1233,7 @@ test "follows take precedence over domain blocks" do
good_eggo = insert(:user, %{ap_id: "https://meanies.social/user/cuteposter"}) good_eggo = insert(:user, %{ap_id: "https://meanies.social/user/cuteposter"})
{:ok, user} = User.block_domain(user, "meanies.social") {:ok, user} = User.block_domain(user, "meanies.social")
{:ok, user} = User.follow(user, good_eggo) {:ok, user, good_eggo} = User.follow(user, good_eggo)
refute User.blocks?(user, good_eggo) refute User.blocks?(user, good_eggo)
end end
@ -1267,8 +1267,8 @@ test "get recipients" do
assert Enum.map([actor, addressed], & &1.ap_id) -- assert Enum.map([actor, addressed], & &1.ap_id) --
Enum.map(User.get_recipients_from_activity(activity), & &1.ap_id) == [] Enum.map(User.get_recipients_from_activity(activity), & &1.ap_id) == []
{:ok, user} = User.follow(user, actor) {:ok, user, actor} = User.follow(user, actor)
{:ok, _user_two} = User.follow(user_two, actor) {:ok, _user_two, _actor} = User.follow(user_two, actor)
recipients = User.get_recipients_from_activity(activity) recipients = User.get_recipients_from_activity(activity)
assert length(recipients) == 3 assert length(recipients) == 3
assert user in recipients assert user in recipients
@ -1289,8 +1289,8 @@ test "has following" do
assert Enum.map([actor, addressed], & &1.ap_id) -- assert Enum.map([actor, addressed], & &1.ap_id) --
Enum.map(User.get_recipients_from_activity(activity), & &1.ap_id) == [] Enum.map(User.get_recipients_from_activity(activity), & &1.ap_id) == []
{:ok, _actor} = User.follow(actor, user) {:ok, _actor, _user} = User.follow(actor, user)
{:ok, _actor} = User.follow(actor, user_two) {:ok, _actor, _user_two} = User.follow(actor, user_two)
recipients = User.get_recipients_from_activity(activity) recipients = User.get_recipients_from_activity(activity)
assert length(recipients) == 2 assert length(recipients) == 2
assert addressed in recipients assert addressed in recipients
@ -1311,7 +1311,7 @@ test "hide a user from followers" do
user = insert(:user) user = insert(:user)
user2 = insert(:user) user2 = insert(:user)
{:ok, user} = User.follow(user, user2) {:ok, user, user2} = User.follow(user, user2)
{:ok, _user} = User.deactivate(user) {:ok, _user} = User.deactivate(user)
user2 = User.get_cached_by_id(user2.id) user2 = User.get_cached_by_id(user2.id)
@ -1324,7 +1324,7 @@ test "hide a user from friends" do
user = insert(:user) user = insert(:user)
user2 = insert(:user) user2 = insert(:user)
{:ok, user2} = User.follow(user2, user) {:ok, user2, user} = User.follow(user2, user)
assert user2.following_count == 1 assert user2.following_count == 1
assert User.following_count(user2) == 1 assert User.following_count(user2) == 1
@ -1342,7 +1342,7 @@ test "hide a user's statuses from timelines and notifications" do
user = insert(:user) user = insert(:user)
user2 = insert(:user) user2 = insert(:user)
{:ok, user2} = User.follow(user2, user) {:ok, user2, user} = User.follow(user2, user)
{:ok, activity} = CommonAPI.post(user, %{status: "hey @#{user2.nickname}"}) {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{user2.nickname}"})
@ -1415,10 +1415,10 @@ test ".delete_user_activities deletes all create activities", %{user: user} do
test "it deactivates a user, all follow relationships and all activities", %{user: user} do test "it deactivates a user, all follow relationships and all activities", %{user: user} do
follower = insert(:user) follower = insert(:user)
{:ok, follower} = User.follow(follower, user) {:ok, follower, user} = User.follow(follower, user)
locked_user = insert(:user, name: "locked", is_locked: true) locked_user = insert(:user, name: "locked", is_locked: true)
{:ok, _} = User.follow(user, locked_user, :follow_pending) {:ok, _, _} = User.follow(user, locked_user, :follow_pending)
object = insert(:note, user: user) object = insert(:note, user: user)
activity = insert(:note_activity, user: user, note: object) activity = insert(:note_activity, user: user, note: object)
@ -1776,9 +1776,9 @@ test "follower count is updated when a follower is blocked" do
follower2 = insert(:user) follower2 = insert(:user)
follower3 = insert(:user) follower3 = insert(:user)
{:ok, follower} = User.follow(follower, user) {:ok, follower, user} = User.follow(follower, user)
{:ok, _follower2} = User.follow(follower2, user) {:ok, _follower2, _user} = User.follow(follower2, user)
{:ok, _follower3} = User.follow(follower3, user) {:ok, _follower3, _user} = User.follow(follower3, user)
{:ok, _user_relationship} = User.block(user, follower) {:ok, _user_relationship} = User.block(user, follower)
user = refresh_record(user) user = refresh_record(user)
@ -2019,8 +2019,7 @@ test "updates the counters normally on following/getting a follow when disabled"
assert other_user.following_count == 0 assert other_user.following_count == 0
assert other_user.follower_count == 0 assert other_user.follower_count == 0
{:ok, user} = Pleroma.User.follow(user, other_user) {:ok, user, other_user} = Pleroma.User.follow(user, other_user)
other_user = Pleroma.User.get_by_id(other_user.id)
assert user.following_count == 1 assert user.following_count == 1
assert other_user.follower_count == 1 assert other_user.follower_count == 1
@ -2043,8 +2042,7 @@ test "syncronizes the counters with the remote instance for the followed when en
assert other_user.follower_count == 0 assert other_user.follower_count == 0
Pleroma.Config.put([:instance, :external_user_synchronization], true) Pleroma.Config.put([:instance, :external_user_synchronization], true)
{:ok, _user} = User.follow(user, other_user) {:ok, _user, other_user} = User.follow(user, other_user)
other_user = User.get_by_id(other_user.id)
assert other_user.follower_count == 437 assert other_user.follower_count == 437
end end
@ -2066,7 +2064,7 @@ test "syncronizes the counters with the remote instance for the follower when en
assert other_user.follower_count == 0 assert other_user.follower_count == 0
Pleroma.Config.put([:instance, :external_user_synchronization], true) Pleroma.Config.put([:instance, :external_user_synchronization], true)
{:ok, other_user} = User.follow(other_user, user) {:ok, other_user, _user} = User.follow(other_user, user)
assert other_user.following_count == 152 assert other_user.following_count == 152
end end

View file

@ -675,7 +675,7 @@ test "it accepts messages from actors that are followed by the user", %{
recipient = insert(:user) recipient = insert(:user)
actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"}) actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
{:ok, recipient} = User.follow(recipient, actor) {:ok, recipient, actor} = User.follow(recipient, actor)
object = object =
data["object"] data["object"]

View file

@ -726,7 +726,7 @@ test "does return activities from followed users on blocked domains" do
domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"}) domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
blocker = insert(:user) blocker = insert(:user)
{:ok, blocker} = User.follow(blocker, domain_user) {:ok, blocker, domain_user} = User.follow(blocker, domain_user)
{:ok, blocker} = User.block_domain(blocker, domain) {:ok, blocker} = User.block_domain(blocker, domain)
assert User.following?(blocker, domain_user) assert User.following?(blocker, domain_user)
@ -853,7 +853,7 @@ test "does include announces on request" do
user = insert(:user) user = insert(:user)
booster = insert(:user) booster = insert(:user)
{:ok, user} = User.follow(user, booster) {:ok, user, booster} = User.follow(user, booster)
{:ok, announce} = CommonAPI.repeat(activity_three.id, booster) {:ok, announce} = CommonAPI.repeat(activity_three.id, booster)
@ -1158,13 +1158,13 @@ test "it filters broken threads" do
user2 = insert(:user) user2 = insert(:user)
user3 = insert(:user) user3 = insert(:user)
{:ok, user1} = User.follow(user1, user3) {:ok, user1, user3} = User.follow(user1, user3)
assert User.following?(user1, user3) assert User.following?(user1, user3)
{:ok, user2} = User.follow(user2, user3) {:ok, user2, user3} = User.follow(user2, user3)
assert User.following?(user2, user3) assert User.following?(user2, user3)
{:ok, user3} = User.follow(user3, user2) {:ok, user3, user2} = User.follow(user3, user2)
assert User.following?(user3, user2) assert User.following?(user3, user2)
{:ok, public_activity} = CommonAPI.post(user3, %{status: "hi 1"}) {:ok, public_activity} = CommonAPI.post(user3, %{status: "hi 1"})
@ -1931,13 +1931,13 @@ test "home timeline with default reply_visibility `self`", %{
defp public_messages(_) do defp public_messages(_) do
[u1, u2, u3, u4] = insert_list(4, :user) [u1, u2, u3, u4] = insert_list(4, :user)
{:ok, u1} = User.follow(u1, u2) {:ok, u1, u2} = User.follow(u1, u2)
{:ok, u2} = User.follow(u2, u1) {:ok, u2, u1} = User.follow(u2, u1)
{:ok, u1} = User.follow(u1, u4) {:ok, u1, u4} = User.follow(u1, u4)
{:ok, u4} = User.follow(u4, u1) {:ok, u4, u1} = User.follow(u4, u1)
{:ok, u2} = User.follow(u2, u3) {:ok, u2, u3} = User.follow(u2, u3)
{:ok, u3} = User.follow(u3, u2) {:ok, u3, u2} = User.follow(u3, u2)
{:ok, a1} = CommonAPI.post(u1, %{status: "Status"}) {:ok, a1} = CommonAPI.post(u1, %{status: "Status"})
@ -2030,15 +2030,15 @@ defp public_messages(_) do
defp private_messages(_) do defp private_messages(_) do
[u1, u2, u3, u4] = insert_list(4, :user) [u1, u2, u3, u4] = insert_list(4, :user)
{:ok, u1} = User.follow(u1, u2) {:ok, u1, u2} = User.follow(u1, u2)
{:ok, u2} = User.follow(u2, u1) {:ok, u2, u1} = User.follow(u2, u1)
{:ok, u1} = User.follow(u1, u3) {:ok, u1, u3} = User.follow(u1, u3)
{:ok, u3} = User.follow(u3, u1) {:ok, u3, u1} = User.follow(u3, u1)
{:ok, u1} = User.follow(u1, u4) {:ok, u1, u4} = User.follow(u1, u4)
{:ok, u4} = User.follow(u4, u1) {:ok, u4, u1} = User.follow(u4, u1)
{:ok, u2} = User.follow(u2, u3) {:ok, u2, u3} = User.follow(u2, u3)
{:ok, u3} = User.follow(u3, u2) {:ok, u3, u2} = User.follow(u3, u2)
{:ok, a1} = CommonAPI.post(u1, %{status: "Status", visibility: "private"}) {:ok, a1} = CommonAPI.post(u1, %{status: "Status", visibility: "private"})

View file

@ -281,8 +281,7 @@ test "publish to url with with different ports" do
actor = insert(:user, follower_address: follower.ap_id) actor = insert(:user, follower_address: follower.ap_id)
user = insert(:user) user = insert(:user)
{:ok, _follower_one} = Pleroma.User.follow(follower, actor) {:ok, follower, actor} = Pleroma.User.follow(follower, actor)
actor = refresh_record(actor)
note_activity = note_activity =
insert(:note_activity, insert(:note_activity,

View file

@ -15,7 +15,7 @@ test "it works for incoming accepts which were pre-accepted" do
follower = insert(:user) follower = insert(:user)
followed = insert(:user) followed = insert(:user)
{:ok, follower} = User.follow(follower, followed) {:ok, follower, followed} = User.follow(follower, followed)
assert User.following?(follower, followed) == true assert User.following?(follower, followed) == true
{:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed) {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)

View file

@ -40,8 +40,8 @@ test "incoming blocks successfully tear down any follow relationship" do
|> Map.put("object", blocked.ap_id) |> Map.put("object", blocked.ap_id)
|> Map.put("actor", blocker.ap_id) |> Map.put("actor", blocker.ap_id)
{:ok, blocker} = User.follow(blocker, blocked) {:ok, blocker, blocked} = User.follow(blocker, blocked)
{:ok, blocked} = User.follow(blocked, blocker) {:ok, blocked, blocker} = User.follow(blocked, blocker)
assert User.following?(blocker, blocked) assert User.following?(blocker, blocked)
assert User.following?(blocked, blocker) assert User.following?(blocked, blocker)

View file

@ -35,7 +35,7 @@ test "it works for incoming rejects which are referenced by IRI only" do
follower = insert(:user) follower = insert(:user)
followed = insert(:user, is_locked: true) followed = insert(:user, is_locked: true)
{:ok, follower} = User.follow(follower, followed) {:ok, follower, followed} = User.follow(follower, followed)
{:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed) {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
assert User.following?(follower, followed) == true assert User.following?(follower, followed) == true

View file

@ -15,7 +15,7 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do
mentioned = insert(:user) mentioned = insert(:user)
following = insert(:user) following = insert(:user)
unrelated = insert(:user) unrelated = insert(:user)
{:ok, following} = Pleroma.User.follow(following, user) {:ok, following, user} = Pleroma.User.follow(following, user)
{:ok, list} = Pleroma.List.create("foo", user) {:ok, list} = Pleroma.List.create("foo", user)
Pleroma.List.follow(list, unrelated) Pleroma.List.follow(list, unrelated)

View file

@ -320,7 +320,7 @@ test "gets users statuses", %{conn: conn} do
user_two = insert(:user) user_two = insert(:user)
user_three = insert(:user) user_three = insert(:user)
{:ok, _user_three} = User.follow(user_three, user_one) {:ok, _user_three, _user_one} = User.follow(user_three, user_one)
{:ok, activity} = CommonAPI.post(user_one, %{status: "HI!!!"}) {:ok, activity} = CommonAPI.post(user_one, %{status: "HI!!!"})
@ -568,7 +568,7 @@ test "if user is authenticated", %{local: local, remote: remote} do
test "getting followers", %{user: user, conn: conn} do test "getting followers", %{user: user, conn: conn} do
other_user = insert(:user) other_user = insert(:user)
{:ok, %{id: user_id}} = User.follow(user, other_user) {:ok, %{id: user_id}, other_user} = User.follow(user, other_user)
conn = get(conn, "/api/v1/accounts/#{other_user.id}/followers") conn = get(conn, "/api/v1/accounts/#{other_user.id}/followers")
@ -577,7 +577,7 @@ test "getting followers", %{user: user, conn: conn} do
test "getting followers, hide_followers", %{user: user, conn: conn} do test "getting followers, hide_followers", %{user: user, conn: conn} do
other_user = insert(:user, hide_followers: true) other_user = insert(:user, hide_followers: true)
{:ok, _user} = User.follow(user, other_user) {:ok, _user, _other_user} = User.follow(user, other_user)
conn = get(conn, "/api/v1/accounts/#{other_user.id}/followers") conn = get(conn, "/api/v1/accounts/#{other_user.id}/followers")
@ -587,7 +587,7 @@ test "getting followers, hide_followers", %{user: user, conn: conn} do
test "getting followers, hide_followers, same user requesting" do test "getting followers, hide_followers, same user requesting" do
user = insert(:user) user = insert(:user)
other_user = insert(:user, hide_followers: true) other_user = insert(:user, hide_followers: true)
{:ok, _user} = User.follow(user, other_user) {:ok, _user, _other_user} = User.follow(user, other_user)
conn = conn =
build_conn() build_conn()
@ -599,9 +599,9 @@ test "getting followers, hide_followers, same user requesting" do
end end
test "getting followers, pagination", %{user: user, conn: conn} do test "getting followers, pagination", %{user: user, conn: conn} do
{:ok, %User{id: follower1_id}} = :user |> insert() |> User.follow(user) {:ok, %User{id: follower1_id}, _user} = :user |> insert() |> User.follow(user)
{:ok, %User{id: follower2_id}} = :user |> insert() |> User.follow(user) {:ok, %User{id: follower2_id}, _user} = :user |> insert() |> User.follow(user)
{:ok, %User{id: follower3_id}} = :user |> insert() |> User.follow(user) {:ok, %User{id: follower3_id}, _user} = :user |> insert() |> User.follow(user)
assert [%{"id" => ^follower3_id}, %{"id" => ^follower2_id}] = assert [%{"id" => ^follower3_id}, %{"id" => ^follower2_id}] =
conn conn
@ -637,7 +637,7 @@ test "getting followers, pagination", %{user: user, conn: conn} do
test "getting following", %{user: user, conn: conn} do test "getting following", %{user: user, conn: conn} do
other_user = insert(:user) other_user = insert(:user)
{:ok, user} = User.follow(user, other_user) {:ok, user, other_user} = User.follow(user, other_user)
conn = get(conn, "/api/v1/accounts/#{user.id}/following") conn = get(conn, "/api/v1/accounts/#{user.id}/following")
@ -648,7 +648,7 @@ test "getting following", %{user: user, conn: conn} do
test "getting following, hide_follows, other user requesting" do test "getting following, hide_follows, other user requesting" do
user = insert(:user, hide_follows: true) user = insert(:user, hide_follows: true)
other_user = insert(:user) other_user = insert(:user)
{:ok, user} = User.follow(user, other_user) {:ok, user, other_user} = User.follow(user, other_user)
conn = conn =
build_conn() build_conn()
@ -662,7 +662,7 @@ test "getting following, hide_follows, other user requesting" do
test "getting following, hide_follows, same user requesting" do test "getting following, hide_follows, same user requesting" do
user = insert(:user, hide_follows: true) user = insert(:user, hide_follows: true)
other_user = insert(:user) other_user = insert(:user)
{:ok, user} = User.follow(user, other_user) {:ok, user, _other_user} = User.follow(user, other_user)
conn = conn =
build_conn() build_conn()
@ -677,9 +677,9 @@ test "getting following, pagination", %{user: user, conn: conn} do
following1 = insert(:user) following1 = insert(:user)
following2 = insert(:user) following2 = insert(:user)
following3 = insert(:user) following3 = insert(:user)
{:ok, _} = User.follow(user, following1) {:ok, _, _} = User.follow(user, following1)
{:ok, _} = User.follow(user, following2) {:ok, _, _} = User.follow(user, following2)
{:ok, _} = User.follow(user, following3) {:ok, _, _} = User.follow(user, following3)
res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}") res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}")
@ -1520,7 +1520,7 @@ test "locked accounts" do
test "returns the relationships for the current user", %{user: user, conn: conn} do test "returns the relationships for the current user", %{user: user, conn: conn} do
%{id: other_user_id} = other_user = insert(:user) %{id: other_user_id} = other_user = insert(:user)
{:ok, _user} = User.follow(user, other_user) {:ok, _user, _other_user} = User.follow(user, other_user)
assert [%{"id" => ^other_user_id}] = assert [%{"id" => ^other_user_id}] =
conn conn

View file

@ -18,7 +18,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
user_two = insert(:user) user_two = insert(:user)
user_three = insert(:user) user_three = insert(:user)
{:ok, user_two} = User.follow(user_two, user_one) {:ok, user_two, user_one} = User.follow(user_two, user_one)
{:ok, %{user: user_one, user_two: user_two, user_three: user_three, conn: conn}} {:ok, %{user: user_one, user_two: user_two, user_three: user_three, conn: conn}}
end end

View file

@ -21,7 +21,7 @@ test "/api/v1/follow_requests works", %{user: user, conn: conn} do
other_user = insert(:user) other_user = insert(:user)
{:ok, _, _, _activity} = CommonAPI.follow(other_user, user) {:ok, _, _, _activity} = CommonAPI.follow(other_user, user)
{:ok, other_user} = User.follow(other_user, user, :follow_pending) {:ok, other_user, user} = User.follow(other_user, user, :follow_pending)
assert User.following?(other_user, user) == false assert User.following?(other_user, user) == false
@ -35,7 +35,7 @@ test "/api/v1/follow_requests/:id/authorize works", %{user: user, conn: conn} do
other_user = insert(:user) other_user = insert(:user)
{:ok, _, _, _activity} = CommonAPI.follow(other_user, user) {:ok, _, _, _activity} = CommonAPI.follow(other_user, user)
{:ok, other_user} = User.follow(other_user, user, :follow_pending) {:ok, other_user, user} = User.follow(other_user, user, :follow_pending)
user = User.get_cached_by_id(user.id) user = User.get_cached_by_id(user.id)
other_user = User.get_cached_by_id(other_user.id) other_user = User.get_cached_by_id(other_user.id)

View file

@ -136,7 +136,7 @@ test "the public timeline includes only public statuses for an authenticated use
test "doesn't return replies if follower is posting with blocked user" do test "doesn't return replies if follower is posting with blocked user" do
%{conn: conn, user: blocker} = oauth_access(["read:statuses"]) %{conn: conn, user: blocker} = oauth_access(["read:statuses"])
[blockee, friend] = insert_list(2, :user) [blockee, friend] = insert_list(2, :user)
{:ok, blocker} = User.follow(blocker, friend) {:ok, blocker, friend} = User.follow(blocker, friend)
{:ok, _} = User.block(blocker, blockee) {:ok, _} = User.block(blocker, blockee)
conn = assign(conn, :user, blocker) conn = assign(conn, :user, blocker)
@ -165,7 +165,7 @@ test "doesn't return replies if follow is posting with users from blocked domain
%{conn: conn, user: blocker} = oauth_access(["read:statuses"]) %{conn: conn, user: blocker} = oauth_access(["read:statuses"])
friend = insert(:user) friend = insert(:user)
blockee = insert(:user, ap_id: "https://example.com/users/blocked") blockee = insert(:user, ap_id: "https://example.com/users/blocked")
{:ok, blocker} = User.follow(blocker, friend) {:ok, blocker, friend} = User.follow(blocker, friend)
{:ok, blocker} = User.block_domain(blocker, "example.com") {:ok, blocker} = User.block_domain(blocker, "example.com")
conn = assign(conn, :user, blocker) conn = assign(conn, :user, blocker)
@ -336,7 +336,7 @@ test "direct timeline", %{conn: conn} do
user_one = insert(:user) user_one = insert(:user)
user_two = insert(:user) user_two = insert(:user)
{:ok, user_two} = User.follow(user_two, user_one) {:ok, user_two, user_one} = User.follow(user_two, user_one)
{:ok, direct} = {:ok, direct} =
CommonAPI.post(user_one, %{ CommonAPI.post(user_one, %{

View file

@ -30,7 +30,7 @@ test "following for user" do
test "returns ok if user already followed" do test "returns ok if user already followed" do
follower = insert(:user) follower = insert(:user)
user = insert(:user) user = insert(:user)
{:ok, follower} = User.follow(follower, user) {:ok, follower, user} = User.follow(follower, user)
{:ok, follower} = MastodonAPI.follow(follower, refresh_record(user)) {:ok, follower} = MastodonAPI.follow(follower, refresh_record(user))
assert User.following?(follower, user) assert User.following?(follower, user)
end end
@ -41,8 +41,8 @@ test "returns user followers" do
follower1_user = insert(:user) follower1_user = insert(:user)
follower2_user = insert(:user) follower2_user = insert(:user)
user = insert(:user) user = insert(:user)
{:ok, _follower1_user} = User.follow(follower1_user, user) {:ok, _follower1_user, _user} = User.follow(follower1_user, user)
{:ok, follower2_user} = User.follow(follower2_user, user) {:ok, follower2_user, _user} = User.follow(follower2_user, user)
assert MastodonAPI.get_followers(user, %{"limit" => 1}) == [follower2_user] assert MastodonAPI.get_followers(user, %{"limit" => 1}) == [follower2_user]
end end
@ -55,9 +55,9 @@ test "returns user friends" do
followed_two = insert(:user) followed_two = insert(:user)
followed_three = insert(:user) followed_three = insert(:user)
{:ok, user} = User.follow(user, followed_one) {:ok, user, followed_one} = User.follow(user, followed_one)
{:ok, user} = User.follow(user, followed_two) {:ok, user, followed_two} = User.follow(user, followed_two)
{:ok, user} = User.follow(user, followed_three) {:ok, user, followed_three} = User.follow(user, followed_three)
res = MastodonAPI.get_friends(user) res = MastodonAPI.get_friends(user)
assert length(res) == 3 assert length(res) == 3

View file

@ -274,8 +274,8 @@ test "represent a relationship for the following and followed user" do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, user} = User.follow(user, other_user) {:ok, user, other_user} = User.follow(user, other_user)
{:ok, other_user} = User.follow(other_user, user) {:ok, other_user, user} = User.follow(other_user, user)
{:ok, _subscription} = User.subscribe(user, other_user) {:ok, _subscription} = User.subscribe(user, other_user)
{:ok, _user_relationships} = User.mute(user, other_user, %{notifications: true}) {:ok, _user_relationships} = User.mute(user, other_user, %{notifications: true})
{:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, other_user) {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, other_user)
@ -301,7 +301,7 @@ test "represent a relationship for the blocking and blocked user" do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, user} = User.follow(user, other_user) {:ok, user, other_user} = User.follow(user, other_user)
{:ok, _subscription} = User.subscribe(user, other_user) {:ok, _subscription} = User.subscribe(user, other_user)
{:ok, _user_relationship} = User.block(user, other_user) {:ok, _user_relationship} = User.block(user, other_user)
{:ok, _user_relationship} = User.block(other_user, user) {:ok, _user_relationship} = User.block(other_user, user)

View file

@ -47,7 +47,8 @@ test "it imports follow lists from file", %{conn: conn} do
|> json_response_and_validate_schema(200) |> json_response_and_validate_schema(200)
assert [{:ok, job_result}] = ObanHelpers.perform_all() assert [{:ok, job_result}] = ObanHelpers.perform_all()
assert job_result == [user2] assert job_result == [refresh_record(user2)]
assert [%Pleroma.User{follower_count: 1}] = job_result
end end
end end
@ -108,7 +109,7 @@ test "it imports follows with different nickname variations", %{conn: conn} do
|> json_response_and_validate_schema(200) |> json_response_and_validate_schema(200)
assert [{:ok, job_result}] = ObanHelpers.perform_all() assert [{:ok, job_result}] = ObanHelpers.perform_all()
assert job_result == users assert job_result == Enum.map(users, &refresh_record/1)
end end
end end

View file

@ -403,6 +403,67 @@ test "it sends follow activities to the 'user:notification' stream", %{
assert notif.activity.id == follow_activity.id assert notif.activity.id == follow_activity.id
refute Streamer.filtered_by_user?(user, notif) refute Streamer.filtered_by_user?(user, notif)
end end
test "it sends follow relationships updates to the 'user' stream", %{
user: user,
token: oauth_token
} do
user_id = user.id
user_url = user.ap_id
other_user = insert(:user)
other_user_id = other_user.id
body =
File.read!("test/fixtures/users_mock/localhost.json")
|> String.replace("{{nickname}}", user.nickname)
|> Jason.encode!()
Tesla.Mock.mock_global(fn
%{method: :get, url: ^user_url} ->
%Tesla.Env{status: 200, body: body}
end)
Streamer.get_topic_and_add_socket("user", user, oauth_token)
{:ok, _follower, _followed, _follow_activity} = CommonAPI.follow(user, other_user)
assert_receive {:text, event}
assert %{"event" => "pleroma:follow_relationships_update", "payload" => payload} =
Jason.decode!(event)
assert %{
"follower" => %{
"follower_count" => 0,
"following_count" => 0,
"id" => ^user_id
},
"following" => %{
"follower_count" => 0,
"following_count" => 0,
"id" => ^other_user_id
},
"state" => "follow_pending"
} = Jason.decode!(payload)
assert_receive {:text, event}
assert %{"event" => "pleroma:follow_relationships_update", "payload" => payload} =
Jason.decode!(event)
assert %{
"follower" => %{
"follower_count" => 0,
"following_count" => 1,
"id" => ^user_id
},
"following" => %{
"follower_count" => 1,
"following_count" => 0,
"id" => ^other_user_id
},
"state" => "follow_accept"
} = Jason.decode!(payload)
end
end end
describe "public streams" do describe "public streams" do
@ -563,7 +624,7 @@ test "it doesn't send unwanted DMs to list", %{user: user_a, token: user_a_token
user_b = insert(:user) user_b = insert(:user)
user_c = insert(:user) user_c = insert(:user)
{:ok, user_a} = User.follow(user_a, user_b) {:ok, user_a, user_b} = User.follow(user_a, user_b)
{:ok, list} = List.create("Test", user_a) {:ok, list} = List.create("Test", user_a)
{:ok, list} = List.follow(list, user_b) {:ok, list} = List.follow(list, user_b)
@ -599,7 +660,7 @@ test "it doesn't send unwanted private posts to list", %{user: user_a, token: us
test "it sends wanted private posts to list", %{user: user_a, token: user_a_token} do test "it sends wanted private posts to list", %{user: user_a, token: user_a_token} do
user_b = insert(:user) user_b = insert(:user)
{:ok, user_a} = User.follow(user_a, user_b) {:ok, user_a, user_b} = User.follow(user_a, user_b)
{:ok, list} = List.create("Test", user_a) {:ok, list} = List.create("Test", user_a)
{:ok, list} = List.follow(list, user_b) {:ok, list} = List.follow(list, user_b)